Книга
Книга
АРХИТЕКТУРА
И ПРОГРАММИРОВАНИЕ МИРКРО-ЭВМ
MICROCOMPUTER ARCHITECTURE AND PROGRAMMING
John F. Wakerly Stanford University
John Wiley & Sons, New York-Chichester-Brisbane-Toronto 1981
ДЖ. УОКЕРЛИДЖ. УОКЕРЛИАРХИТЕКТУРА И ПРОГРАММИРОВАНИЕ МИКРО-ЭВМ
ДЖ. УОКЕРЛИ
ДЖ. УОКЕРЛИ
В 2-х КНИГАХ
Перевод с английского под редакцией д-ра техн, наук А. Г. Филиппова
Москва «Мир» 1984
ББК 32.973
У62 УДК 681.3
Уокерли Дж.
У 62 Архитектура и программирование микро-ЭВМ: В 2-х книгах. Пер. с англ. — М.: Мир, 1984. — Кн. 1. 486 с., ил.
Труд американского ученого посвящен общим принципам построения и программирования ЭВМ и их реализации при создании микро-ЭВМ разного типа. В русском переводе выпускается в 2-х книгах.
В книге 1 подробно освещаются архитектура ЭВМ, вопросы программирования как на языке ассемблера, так и на языке Паскаль. Рассматриваются проблемы, связанные с организацией ввода-вывода, системы прерываний и прямого доступа к памяти.
Для специалистов, связанных с разработкой и применением микропроцессорных систем, а также аспирантов и студентов соответствующих специальностей вузов.
ББК 32.973 6Ф7.3ББК 32.973 6Ф7.32405000000-103 ол -
ББК 32.973 6Ф7.3
ББК 32.973 6Ф7.3
041(01)-84,66-84’ Ч- *
Редакция литературы по новой технике
Copyright © 1981 by John Wiley & Sons, Inc. All rights reserved. Authorized translation from english language edition published by John Wiley & Sons, Inc.
© Перевод на русский язык, «Мир», 1984
ПРЕДИСЛОВИЕ РЕДАКТОРА ПЕРЕВОДА
В настоящее время проектирование микропроцессорных систем и микро- ЭВМ определилось как самостоятельное направление развития вычислительной техники. Важнейшими качествами микропроцессоров и микро-ЭВМ по сравнению с вычислительными устройствами других классов являются: малые габариты, низкая стоимость, возможность массового производства, простота обслуживания; кроме того, микро-ЭВМ несложно встраивать в любое устройство или систему. Именно эти качества микропроцессорных устройств определили основные области их применения. Они широко используются в станках с числовым программным управлением, промышленных роботах, системах управления технологическими процессами и транспортными средствами, в контрольно-испытательном оборудовании, информационно-измерительных системах и др. Современные микро-ЭВМ обладают также достаточными во многих случаях вычислительными возможностями для использования их в качестве ЭВМ индивидуального пользования на рабочем месте инженера-проектировщика, конструктора, экономиста, исследователя. Для расширения возможностей такой микро-ЭВМ она может быть подключена в качестве интеллектуального терминала к более мощной ЭВМ. Кроме того, микро-ЭВМ может использоваться в составе разветвленной иерархической сети ЭВМ в качестве низшего звена иерархии. Благодаря низкой стоимости, малым габаритам и массовому производству микро-ЭВМ появилась возможность применения вычислительной техники в таких областях человеческой деятельности, где совсем недавно это не представлялось целесообразным в силу либо технических, либо экономических причин.
Понятно в связи с этим то большое значение, которое придается в последнее время подготовке специалистов, в совершенстве владеющих своей основной профессией и в то же время имеющих четкое представление о возможностях микропроцессоров и микро-ЭВМ, умеющих эффективно использовать их в своей области.
Решению этой задачи в определенной мере должно способствовать издание учебных пособий по микропроцессорам и микро-ЭВМ; недостаток которых в настоящее время ощущается довольно остро. Несмотря на то что за последние годы в нашей стране выпущено более полутора десятка книг по данному разделу вычислительной техники, лишь очень немногие из них по методике изложения материала и по комплексу освещенных вопросов могут быть отнесены к учебникам.
Предлагаемую вниманию читателей книгу можно с полным основанием отнести к числу немногих изданных в нашей стране учебных пособий по микропроцессорам и микро-ЭВМ. В сущности, данная книга является первым фундаментальным учебным пособием по этой отрасли знаний.
Книга написана по материалам курса лекций, прочитанных автором в Стэнфордском университете (США), а также на основе его большого опыта
в области микропроцессорной техники. Ее отличают методически хорошо продуманное изложение и удачный отбор материала.
Рассмотрение вопросов сопровождается большим количеством схем, диаграмм, таблиц. Особенности программирования микро-ЭВМ иллюстрируются многочисленными примерами программ, разработанных и отлаженных под руководством автора. В конце каждой главы приведены упражнения, которые дают возможность читателю закрепить изученный материал.
Книга предназначена для широкого круга специалистов, связанных с разработкой микропроцессорных систем и микро-ЭВМ и их применением в различных отраслях народного хозяйства. Она также будет полезна студентам и аспирантам, специализирующимся в области вычислительной техники, автоматики и электроники.
В работе по переводу книги участвовали: С. А. Кузнецов (гл. 1, 4, 5, 8, 17), канд. техн, наук Л. В. Шабанов (гл. 2, 3, 18, прил. А, Б), Ю. С. Петров (гл. 6, 7, 9, 13, 19), А. И. Роговский (гл. 10, 16), М. К. Бушуев (гл. 11), Н. П. Фурсиков (гл. 12, 14), А. А. Рябчиков (гл. 15, прил. В).
А. Г. Филиппов
ПРЕДИСЛОВИЕ
Посвящается Риджине Мари, моей маленькой дочери
Наиболее существенным достоинством микропроцессоров, появление которых вызвало революцию в области вычислительной техники, является их дешевизна. Этот новый вид вычислительных устройств не привел к значительным изменениям основных принципов построения ЭВМ и системы программирования. В то же время с распространением микропроцессорной техники еще больше увеличилась потребность в высококвалифицированных программистах.
Цель данной книги заключается в изложении основ построения ЭВМ и Программных средств с использованием в качестве примеров микропроцессорных систем. Большое внимание уделяется основным принципам организации микропроцессоров, а не детальному описанию набора команд какой-либо конкретной модели. Однако рассмотрение основных принципов должно проводиться на базе некоторой определенным образом построенной ЭВМ. С этой целью в книге используются два «гипотетических» процессора, характеристики и наборы команд которых являются подмножеством соответствующих характеристик и наборов команд двух реальных процессоров: 6809 фирмы Motorola и Z8000 фирмы Zilog. Наряду с изложением основных принципов организации микропроцессорных систем дано подробное описание семи современных типов микропроцессоров.
Книга состоит из трех частей. Часть I содержит вводный материал, который, очевидно, знаком многим читателям. Ключевой является часть II книги, в которой на примере двух гипотетических процессоров изложены основные принципы организации ЭВМ и программирования на языке ассемблера. В части III, исходя из этих принципов, дано достаточно подробное и четкое описание архитектуры семи наиболее распространенных типов микропроцессоров: PDP-11/LSI-11 фирмы DEC, 68000 и 6809 фирмы Motorola, Z8000 фирмы Zilog, 9900 фирмы Texas Instruments, 8086 и MCS-48 фирмы Intel.
Назначение книги
Данная книга может быть использована для составления вводного курса по организации ЭВМ (в том числе микро-ЭВМ) и программированию на языке ассемблера, который обычно является третьим курсом программы по вычислительной технике. (В первых двух курсах рассматриваются принципы программирования на языках высокого уровня, таких, как Паскаль или Фортран.) В таком вводном курсе целесообразно изложить основные принципы организации ЭВМ (части I и II книги), а также привести описание одного из процессоров (часть III). В настоящей книге рассмотрены все вопросы, рекомендуемые для третьего курса программы по вычислительной технике, и обращено внимание на средства, используемые для достижения хорошего стиля программирования в соответствии с рекомендациями Ассоциации по вычислительной технике \Comm. АСМ, 22, 3, 147-166 (March 1979)].
Разработка основных концепций (части I и II) и их последовательное применение при описании различных процессоров (часть III) делают эту книгу пригодной также и для составления курса «архитектура микро-ЭВМ». Целью такого курса являются изучение и сравнительный анализ структуры и характеристик различных вычислительных систем.
Наконец, данная книга может использоваться в качестве справочника как изучавшими ее студентами, так и специалистами по вычислительной технике. Возможности такого назначения книги способствуют структура ее построения, однозначно определяющая круг рассматриваемых в том или ином разделе вопросов, а также исчерпывающий предметный указатель.
Содержание книги
Главы 1—4 составляют часть I книги и включают вводный материал, который в дальнейшем используется в других частях.
В гл. 1 даны определения используемых терминов и рассмотрены важнейшие понятия из области вычислительной техники, так что вдумчивый студент может изучить содержание последующих частей книги без специальной предварительной подготовки.
В гл. 2 описан Паскаль — наиболее распространенный структурный язык высокого уровня для вычислительных систем на микропроцессорах. На протяжении всей книги он используется для документирования алгоритмов и определения внутренних операций ЭВМ, так что умение читать простые программы на Паскале является необходимым. Эта глава по существу является введением в Паскаль.
Структуры данных, включая массивы, стеки и очереди, описаны в гл. 3 Указанные структуры данных используются впоследствии при изложении некоторых аспектов организации ЭВМ и программирования.
В гл. 4 дано представление о системах счисления и арифметических операциях, реализуемых в различных вычислительных системах.
В части II освещены общие принципы построения ЭВМ на примере двух гипотетических процессоров Н6809 и Н8000, наборы команд и характеристики которых являются подмножеством наборов команд и характеристик двух реальных процессоров. Прототипом первого гипотетического процессора является микропроцессор 6809 фирмы Motorola, второго — микропроцессор Z8000 фирмы Zilog.
В гл. 5 рассмотрены наборы команд, организация и язык ассемблера указанных гипотетических процессоров. В главу включен дополнительный раздел по гипотетической машине с использованием стека.
Более полное описание ассемблеров и средств программирования на языке ассемблера дано в гл. 6. Здесь изложены вопросы перемещения и установления связей программ, организации позиционно независимых программ, использования макрокоманд и структурных языков ассемблера.
Все распространенные способы адресации, используемые в различных процессорах, приведены в гл. 7. Рассмотрены также вопросы, связанные с преобразованием адресов памяти и управлением памятью.
В гл. 8 указаны форматы команд и действия, производимые при выполнении основных операций в ЭВМ.
В гл. 9 проведен анализ процедур и функций в языке Паскаль и показано, как концепции языка высокого уровня соотносятся с порядком вызова подпрограмм и соглашениями о передаче параметров в программах на языке ассемблера. Рассмотрены также понятия рекурсии и сопрограмм.
Глава 10 посвящена организации ввода-вывода и соответствующим программным средствам.
Вопросы обработки прерываний и прямого доступа к памяти, а также современные представления о процессах, наборах данных коллективного пользования и реентерабельности нашли отражение в гл. 11.
В гл. 12 приведен материал по организации разработки программного обеспечения.
Часть III состоит из гл. 13—19, в каждой из которых, исходя из принципов, сформулированных в части II, дано описание архитектуры конкретной микро-ЭВМ. Вполне естественно, что вначале приведена архитектура ЭВМ PDP-11 и LSI-И, поскольку в основу всех современных микро-ЭВМ положены принципы организации мини-ЭВМ PDP-11. Далее рассмотрены следующие процессоры: 68000 фирмы Motorola, Z8000 фирмы Zilog, 9900 фирмы Texas Instruments, 6809 фирмы Motorola, 8086 и MCS-48 фирмы Intel. Каждая глава части III состоит из шести разделов, материал в которых изложен в том же порядке, что и в главах части И: основные принципы организации; язык ассемблера; способы адресации; набор команд; примеры программ; организация ввода-вывода и прерываний.
Описание процессоров в части III является достаточно полным, так чтобы читатель был в состоянии дать процессорам правильную оценку. Для каждого процессора подробно разобраны способы адресации и наборы команд, но вследствие ограниченного объема книги исключены из рассмотрения некоторые вопросы, касающиеся языков ассемблера, появления побочных эффектов, возникающих при выполнении команд, и функционирования систем разработки. Отсутствуют также сведения о структуре каналов, синхронизации, интерфейсах и др. Читателю, который намеревается составлять программы для одного из процессоров, описанных в части III, а затем прогонять их на этом процессоре, кроме данной книги может понадобиться документация, выпускаемая фирмой-изготовителем процессора.
В конце книги помещены три приложения. В приложении А приведены код ASCII и специальные символы, используемые в терминалах с дисплеями. В приложении Б рассмотрена расширенная версия языка Паскаль для выполнения операций над битами. Заметим, что описание расширенной .версии этого языка дано также в гл. 4, 5 и 8. Приложение В посвящено организации асинхронного последовательного протокола связи и интерфейсам, обычно используемым с дисплеями.
Как создавалась книга
Первоначальный вариант рукописи книги подготавливался с помощью программ обработки слов на моей собственной микро-ЭВМ. Окончательный вариант, хранимый в памяти ЭВМ, с помощью специальной программы был приведен к виду, в котором его можно было вводить непосредственно в фотолинотип для производства гранок, что позволило избежать этапа набора, в процессе которого, как правило, в текст вносится значительное количество ошибок. Подобный способ создания книги с привлечением больших ЭВМ использовали многие авторы изданий по вычислительной технике. Следует отметить, что двое из них разработали свои собственные средства обработки слов (Knuth D. Е., ТЕХ and Metafont, Digital Press, 1979; Kernighan В. W., Software Tools, Addison-Wesley, 1976; Kernighan B. W., The Elements of Programming Style, 2nd ed., McGraw-Hill, 1978; Kernighan B. W., The C Programming Language, Prentice-Hall, 1978). Однако я полагаю, что данная книга является одной из первых, выпущенных с применением дешевой вычислительной системы на микропроцессоре. Поскольку этот вопрос может представлять интерес для читателей, остановимся на нем более подробно.
Я воспользовался следующими вычислительными средствами: ЭВМ фир- мы Southwest Technical Products, разработанной на базе микропроцессора 6800, с памятью 48К байт, внешним запоминающим устройством на гибких дисках BFD-68 фирмы Smoke Signal Broadcasting, операционной системой DOS68, терминалом с дисплеем ADM-3A, печатающим устройством Diablo 1620 и устройством чтения/перфорации бумажной ленты. Общая стоимость аппаратуры в 1978 г. составила сумму значительно ниже 10 000 долл.
Из использованных программ наиболее важными являются следующие: редактор текстов, программа задания формата текста и транслятор для линотипа. Последние два года я провел, тратя больше времени на «общение» с программой редактора текстов, чем с какой-либо другой программой или человеком, и считаю, что мне повезло, так как в моем распоряжении был прекрасный, ориентированный на работу с дисплеем редактор текстов. Эта программа разработана Томом Кросли и называется 6800 PIE (PIE является аббревиатурой словосочетания Programma International Editor). Том написал также версию этой программы для ЭВМ Apple II.
При создании книги я использовал редактор для получения файлов, содержащих неформатированный текст, и команды задания формата, например: «Начало параграфа», «Начало заголовка раздела», «Сменить шрифт» и т. п. Затем текстовые файлы обрабатывались программой задания формата для оформления текста соответствующим образом. Программа задания формата составлена фирмой Technical Systems Consultants для процессора 6800 на основе программы редактора NROFF системы UNIX фирмы Bell Laboratories и усовершенствована мною для обеспечения возможности задания книжных форматов и параметров линотипов.
Управление программой задания формата осуществляется с помощью макрокоманд, действие которых определяется соответствующими файлами макрокоманд. В моем распоряжении было два таких файла, один из которых использовался для оформления рукописи в виде, удобном для вывода на печатающее устройство Diablo, а второй — для подготовки рукописи к обработке транслятором для линотипа и вводу в фотолинотип.
Таким образом, у меня имелась возможность получать точные копии типографского текста в виде распечаток, которые были прочитаны сотнями студентов. Поскольку исходным как для типографского текста, так и для распечаток являлся один и тот же файл с неформатированным текстом, все ошибки, найденные в распечатках, могли быть исправлены в этом файле с почти стопроцентной гарантией, что в процессе издания книги в текст не будет внесено новых ошибок. В результате выпущенная книга не содержит ошибок (канешно, на столько на сколько несодержит ашибок орегинальная авторская рукопесь).
В состав вычислительной системы, как было отмечено, входило устройство чтения/перфорации. Как выяснилось, технология набора меняется настолько медленно, что единственным стандартным носителем вводимых данных является бумажная перфолента, содержащая информацию в некоем 6-битовом коде TTS. Вследствие этого мне пришлось разработать программу, осуществляющую перевод из кода ASCII в код ITS, и производить вывод текста на 6-дорожечную перфоленту для последующего ввода в фотолинотип. Перфоленты проявили себя наиболее слабым звеном системы: одна ошибка на 10— 20 страниц книги являлась следствием использования этого носителя данных. Благодаря эффективной проверке копий рукописи студентами гранки рукописи были практически совершенными, за исключением ошибок перфорации. Вследствие этого не представлялось целесообразным привлекать к проверке текста корректора для выявления одной ошибки на 10—20 страницах текста, в то время как при обычном наборе в среднем бывает по две ошибки на одной странице. Поэтому мы приносим свои извинения за оставшиеся в тексте ошибки, появившиеся в результате использования перфолент или по каким-либо другим причинам.
Ошибки ЯНИНИН
Как известно, нет программ без ошибок, имеются лишь программы с не- выявленными ошибками. Это утверждение справедливо и в отношении книги Я буду весьма признателен за сообщения об ошибках в этой книге, с тем чтобы иметь возможность исправить их в последующих изданиях.
Выражение признательности
Я благодарен многим лицам за их усилия, направленные на то, чтобы появилась эта книга. В первую очередь это заслуга (или вина) редактора издательства Wiley Джина Дейвенпорта, уговорившего меня взяться за перо. Его метод убеждения состоял в том, что он посылал мне на отзыв проспекты на книги по микро-ЭВМ других авторов, на что я обычно отвечал: «Нет, они сделали все неверно; если бы они разбирались в этом получше, то сделали бы «как надо»». В конце концов мне пришлось подкрепить свои отзывы выпуском собственной книги, написанной «как надо». Теперь, я полагаю, кто-то другой будет составлять отзыв на мою книгу, что побудит его создать свой, улучшенный вариант изложения данного предмета.
Хэролд Стоун дал очень ценные рекомендации, касающиеся плана и предварительного варианта рукописи, которые помогли мне довести книгу до ее настоящего вида. Замечания, полученные от Джоула Боуни, Дейва Ходжеса, Джона Хеннеси, Дэниса Элисона, Майка Малдера, Марты Слоун, Скипа Стриттера, Джона Золновски и др. были также очень полезны. Хэмид Ней- беви тщательнейшим образом проработал рукопись с целью соблюдения точного употребления терминов и последовательности изложения и сделал десятки исправлений и изменений. Я благодарен всем упомянутым лицам за вклад» который они внесли в создание этой книги
. Более пятисот студентов, прослушавших за последние два года вводный курс по организации ЭВМ и программированию на языке ассемблера в Стэнфордском университете, пользовались копиями рукописи — распечатками текста. Обсуждение с ними некоторых вопросов побудило меня внести в книгу ряд улучшений, и, кроме того, с их помощью было выявлено около двухсот технологических ошибок, что позволило своевременно исправить рукопись.
Я хочу поблагодарить всех студентов, оказавших мне помощь в работе над книгой, а также моих ассистентов Глена Хана, Питера Ван Сикеля, Элен Ханзайкер, Сузан Робертс и Дэвида Лэкрица. Я признателен Мари Ноэль Лу за прекрасно выполненную перепечатку записей лекций в сжатые сроки. Особую благодарность я хотел бы выразить руководителям фирмы Motorola за любезное предоставление в наше пользование системы разработки EXORciser, ориентированной на процессор 6809, а также Национальному научному фонду за содействие в приобретении терминалов и другого оборудования.
Многие авторы имеют лишь поверхностные знания о технологическом процессе изготовления книги, я же являлся его активным участником. Поэтому я хочу поблагодарить всех сотрудников фирмы Holmes Composition за их помощь в этом новом для меня деле и особенно Боба Холмса, помогавшего мне в овладении профессией линотиписта. Я хочу также выразить признательность Линде Марсетти за ее прекрасную работу по оформлению книги и Бобу Бэлинджеру (издательство Wiley) за координацию работ по изданию книги.
Я весьма обязан моему другу Тому Кросли за поиск и совершенствование средств обработки текстов, которыми я пользовался при создании рукописи.
В ноябре 1978 г., когда я приступал к работе над книгой, я взял шестимесячный отпуск в фирме BNR INC, сотрудником которой я в то время был. Я собирался написать эту книгу, а также книгу по аппаратным средствам микро-ЭВМ за шесть месяцев! Фирме BNR я могу лишь выразить свою признательность за предоставленный мне отпуск.
И, конечно, я должен поблагодарить мою жену Кейт за то, что она мирилась с моей работой допоздна и не теряла самообладания, когда возникали затруднения, столь неизбежные при создании такой книги. Моя дочь Джина, родившаяся ровно два года назад, за последние три месяца научилась говорить больше новых слов, чем я написал, так что, пожалуй, настало время поставить точку и перейти от написания книги к ее печатанию.
Джон Ф. У оперлиДжон Ф. У оперлиМаунтен-Вью, Калифорния 13 ноября 1980 г.
Джон Ф. У оперли
Джон Ф. У оперли
ПРЕДИСЛОВИЕ
ДЛЯ ПРЕПОДАВАТЕЛЕЙ
Это предисловие предназначено для того, чтобы помочь преподавателям ориентироваться в материале, содержащемся в данной книге. Читатели, пользующиеся книгой как справочником, для того чтобы найти интересующие их сведения, могут обратиться к соответствующему разделу или предметному указателю.
Из оглавления следует, что книга состоит из ряда логически связанных разделов, в каждом из которых глубоко исследуется тот или иной вопрос. Поэтому книга является исчерпывающим справочником и включает гораздо больше информации, чем какой-либо вводный курс по организации ЭВМ. и программированию на языке ассемблера. В обычных курсах содержится лишь от половины до двух третей материала частей I и II и описание только одного из процессоров, рассмотренных в части III.
Когда я. приступал к работе над книгой, мои коллеги предлагали, чтобы по своей структуре она представляла собой набор небольших по объему разделов, посвященных изучению той или иной темы. Разделы должны быть расположены в таком порядке, чтобы оглавление книги примерно соответствовало плану учебного курса. Основная проблема, которая, на мой взгляд, встала бы передо мной при таком подходе, заключалась бы в том, что именно мне пришлось решать, какой материал и в каком порядке должен быть изложен. Однако у каждого преподавателя имеется своя методика обучения. Я, например, в своей преподавательской деятельности не придерживаюсь одного и того же плана курса лекций от семестра к семестру.
Поэтому я остановился на такой структуре книги, которая позволяет преподавателям легко формировать из материалов книги оптимальный курс лекций,- соответствующий составу студенческой аудитории и имеющемуся ресурсу времени. Это требует большего расхода времени на выполнение предварительной работы по составлению курса, чем при буквальном «следовании книге», зато дает два преимущества. Во-первых, вы можете отобрать материал, который хотите включить в курс, без просмотра значительного объема литературы, посвященной данному вопросу. Во-вторых, по завершении изучения курса у студентов останется исчерпывающий справочник, в котором они могут найти даже тот материал, который не был включен в лекции, например описание различных процессоров, с которыми им, возможно, придется рабо*- тать в будущем.
Возможный план курса
. На тот случай, если это облегчит вашу работу, я предлагаю ознакомиться с планом курса лекций, которые я читал в Стэнфордском университете. Основная цель курса заключалась в том, чтобы с позиций программиста изложить воцросы программирования на языке ассемблера и организации ЭВМ студентам, которые уже прослушали один или два курса по программирова-
нию на языках высокого уровня. Продолжительность академической четверти составляет десять недель, в течение которых прочитывают тридцать 50-минутных лекций.
В табл. 1 указаны темы, которые я включал в читаемый мною курс, соответствующие им разделы книги и примерное число лекций, посвященных каждой теме. Лекции по темам, перечисленным в верхней части таблицы, я включал в каждый курс, а лекции по темам из нижней части таблицы я читал в конце семестра в зависимости от оставшегося времени и интересоа студентов.
Использование языка Паскаль -
На первых же лекциях я решаю все организационные вопросы и рассматриваю назначение и особенности языка Паскаль. Затем я прошу студентов, не изучавших этот язык, ознакомиться с гл. 2. Хотя я и использую Паскаль для объяснения выполняемых ЭВМ операций и в качестве языка документирования в некоторых примерах программ на языке ассемблера, нет необходимости, чтобы студенты знали его на том же уровне, что и программисты на Паскале. Умение читать элементарные программы на Паскале — это все, что требуется от студентов для понимания примеров с использованием Паскаля, приведенных в данной книге.
Практическая работа по программированию
Мой курс предусматривает использование реальной машины для всех учебных программ, и я требую от студентов, чтобы они разрабатывали для этой машины несколько программ, прогоняли их и отлаживали в лаборатории по программированию. Аналогичные темы могут включаться в курс независимо от того, какая машина используется для выполнения студентами практических работ по программированию. Например, я читал, по существу, один и тот же курс в разных условиях — используя для практических занятий ЭВМ 6809 фирмы Motorola и ЭВМ 21МХ фирмы Hewlett-Packard. Если вы встретите в табл. 1 обозначение типа «п.1», обратитесь к соответствующему разделу одной из глав части III, отведенной конкретной реальной машине.
* При планировании курса необходимо правильно распределить время с Тем, чтобы его хватило не только на чтение лекций, но и на выполнение студентами практических заданий по программированию. Это особенно важно при 10-недельной чет.верти. В курс, который я читал в Стэнфордском университете, входили одно ознакомительное занятие с лабораторией, проводимое в начале изучения курса, и три практических задания.
Для выполнения первого задания по программированию от студента требуется лишь знание основных операций (Загрузить, Записать, Сложить, Переход) и способов адресации (регистровая, непосредственная, абсолютная). В качестве исходных данных для программирования студенты получают написанный на Паскале алгоритм решения таких задач, как перевод из двоичного кода в код ASCII (упр. 5.13) или разложение числа на простые сомножители (упр. 8.12). Операции ввода-вывода выполняются с использованием утилит мониторной системы в ПЗУ. Студенты накапливают практический опыт применения ассемблера и других средств программного обеспечения, реализации возможностей процессора, для которого разрабатывается программа, а также знакомятся с отладкой.
Второе задание способствует приобретению практических навыков использования более сложных способов адресации и нестандартных структур данных, а также составления простых программ для ввода-вывода данных. В семестровом курсе для лучшего усвоения этих тем целесообразно провести две практические работы. Я полагаю, что упр. 7.5 и 10.16 являются идеальными
Обязательные темы
Источник (номер раздела книги)
Количество лекций
Введение и обзор курса
—
0.5
Введение в язык Паскаль
2.1—2.9
1,0
Типы данных в ЭВМ
1,6, 4.12
0,5
Позиционные системы счисления
4.1—4.4
1,0
Дополнительный код
4.5—4.6
1,0
Основы архитектуры ЭВМ
1.3, 5.1—5.2.5, п.1
5.2.6, п.2
1,5
Основы языка ассемблера
0,5
Простые способы адресации
5.2.9, 7.1—7.2.4, п.З
8.1—8.5, п.4
0,8
Простые операции
Практическая работа
Ассемблеры и загрузчики
Коды условий, передача управления
Подпрограммы
Как писать программы и проверять их
0,8 0,4 1,0 0,5 0,5
1,0
6.1—6.3
8.6
5.2.10, 8.6
12.3, 12.5
в лаборатории
Сложные способы адресации
7.2.4—7.3.4
1,0
Использование различных способов адре-
3.1—3.4
2,0
сации
Другие виды операций
8.7—8.8
0,5
Практическая работа
■—
0,5
Подпрограммы и параметры
9.1—9.3.6
2,0
Ввод-вывод
10.1—10.3 пб
1,5
Терминалы
А, В.З
0,5
Очереди
3.4, 9.3.7
1,0
Прерывания
11.1—11.3
1,5
Практическая работа
—
0,5
Обработка прерываний
11.5—11.6
2,0
Факультативные темы
Источник (номер раздела книги)
Количество лекций
Разработка программ
12.1—12.6
1,0
Рекурсия
9.4
1,0
Сопрограммы
9.5
1,0
Организация перемещения и связей под
6.4
0,5
программ Макрокоманды
6.5
0,5
Связанные списки
3.5—3.7
1,0
Умножение и деление
4.8—4.9, 8.10
1,0
Арифметика повышенной точности
8.9
0,5
Десятичная арифметика
4.10, 8.11
0,5
Арифметика с плавающей точкой
4.11
1,0
Стековые машины
5.4
1,0
Классификация ЭВМ
5.5
1,0
Архитектура центральных процессоров
13—19 (выбрать один ЦП)
3,0
Прямой доступ к памяти
11 .4
1,0
Устройства ввода-вывода
Протоколы и устройства последовательно
В. Г—В.З
1,0
го ввода-вывода
для этой цели, поскольку полученная в результате их выполнения программа весьма эффектна. Помимо информации описательного характера я выдаю студентам проект программы, в соответствии с которым задание разбивается на несколько модулей с детально описанными условиями согласования, после чего студенты осуществляют проектирование и кодирование каждого из этих модулей.
Третье задание по программированию является усложнением второго задания: оно предусматривает организацию прерывания для того, чтобы более эффективно реализовать ввод-вывод. В этом случае можно воспользоваться упр. 11.17. Здесь также указываются спецификации модулей, а их проектирование и кодирование предоставляются студентам.
Среда программирования
Обычные микропроцессорные системы разработки программ не в состоянии обеспечить работу огромного числа студентов по редактированию, ассемблированию и другим этапам создания программ. При наличии больших учебных групп из положения можно выйти двояким образом: прежде всего, можно развернуть столь же многочисленные микропроцессорные системы.
Второй способ, которому отдают предпочтение в Стэнфордском университете и во многих других учебных заведениях, заключается в использовании возможностей, заложенных в большой университетской ЭВМ, для выполнения редактирования и кросс-ассемблирования. Объектные файлы, сформированные кросс-ассемблером на университетской ЭВМ, для прогона и отладки загружаются в систему разработки. В целях упрощения загрузки для объектных файлов используется формат в коде ASCII и имеется канал связи для последовательной передачи данных между университетской ЭВМ и системой разработки; кроме того, можно работать с гибкими дисками или магнитными лентами в кассетах. Каналы связи могут быть как коммутируемыми, так и некоммутируемыми. В ПЗУ системы разработки входит небольшая программа, которая обеспечивает создание виртуальной связи между университетской ЭВМ и пультом системы разработки, так что студенты могут записывать информацию на запоминающие устройства большой ЭВМ и загружать Объектные файлы.
Реализация возможностей университетских вычислительных систем при наличии больших учебных групп позволяет достичь ряда преимуществ. Индивидуальные вычислительные системы разработки программного обеспечения не нуждаются в большом объеме памяти; их необходимыми элементами являются центральный процессор, оперативная память, несколько устройств ввода-вывода, системный терминал и терминал пользователя или другое простое устройство ввода-вывода заданий, общая стоимость которых составляет лишь несколько тысяч долларов для одной системы. Поскольку большинство устройств сосредоточено в университетской вычислительной системе, основные проблемы по их сопровождению появляются и решаются в рамках этой системы.
Положительными факторами являются также возможности использования централизованного хранения файлов и передачи данных университетской системы. Например, на внешних запоминающих устройствах системы, применяемой в Стэнфордском университете, хранятся некоторые исходные модули программ, которые студенты могут включать в свои программы. Когда приходит время сдавать задания на проверку, студенты предъявляют ассистенту листинг своей программы на ассемблере, а также копию объектного файла, которая может быть загружена в систему разработки и проверена на отсутствие ошибок.
Наконец, использование большой центральной ЭВМ для разработки программ является стандартным для промышленного производства методом выполнения больших программных проектов, ориентированных на микропроцес-
соры. В целях обеспечения реализации этого метода были разработаны универсальные микропроцессорные вычислительные системы разработки, например 64000 фирмы Hewlett-Packard и 8001 фирмы Tektronix.
Дополнительная литература
Для получения дополнительных сведений преподавателям и читателям следует обратиться к соответствующей гехнической литературе. Им могут понадобиться следующие пособия:
Справочное руководство по процессору, выпускаемое его изготовителем. В этом руководстве исчерпывающим образом описаны система команд процессора и побочные эффекты, возникающие при их выполнении.
Справочное руководство по языку ассемблера, выпускаемое изготовителем процессора или фирмой, поставляющей кросс-ассемблеры. Здесь рассматриваются синтаксис языка ассемблера и выполнение программы ассемблера.
Справочное руководство по системе разработки, выпускаемое изготовителем этой системы. Анализируются работа системы (например, как ее запустить) и возможности поставляемого для нее программного обеспечения (встроенные порты ввода-вывода, утилиты мониторной системы, процедуры и команды, обеспечивающие отладку).
Руководство по проведению работ в лаборатории и по организации используемой системы, подготавливаемое преподавателем. Несколько страниц содержат информацию о порядке записи на лабораторные работы, часах работы лаборатории и др., а также описание вычислительных средств, которыми оснащена лаборатория, их структуры.
В Стэнфордском университете составлено руководство по лабораторным работам для студентов, в котором освещены все перечисленные выше вопросы. Оно включает карманную справочную таблицу по системе 6809; описание кросс-ассемблер а и порядок его использования (занимает 4 страницы); список команд отладки и встроенных процедур в мониторе системы (8 страниц) i описание лабораторного оборудования (5 страниц). Кроме того, указанное руководство содержит упражнение на освоение лабораторного оборудования и небольшое пособие по написанию, прогону и отладке программ. В пособии в основном освещены те вопросы, которые, по наблюдению моего ассистента Питера Ван Сикеля, регулярно возникают у студентов. Большая часть материала этого пособия изложена в разд. 12.5 настоящей книги. Значительная часть приведенного в книге материала может быть использована для курсов, ориентированных на другие вычислительные системы, кроме рассмотренных здесь систем 6809 фирмы Motorola и Z8000 фирмы Zilog.
ВВЕДЕНИЕ
В ВЫЧИСЛИТЕЛЬНУЮ ТЕХНИКУ
И ПРОГРАММИРОВАНИЕ
ЧТО ТАКОЕ ЭВМ?
ЭВМ — это большая комната, заполненная оборудованием^ которое щелкает и звенит, шумит и мигает, удовлетворяя потребности большой организации в обработке данных. Она проводит инвентаризацию, обслуживает клиентов, оплачивает счета кредиторам, редактирует и печатает доклады, осуществляет связь с другими ЭВМ и, что самое главное, каждую вторую среду составляет платежные ведомости. Создается впечатление, что ЭВМ делает все, кроме приготовления завтрака.-
ЭВМ — это стойка с аппаратурой и телетайпом, установленная в углу научной лаборатории. Посредством электрических сигналов и механических устройств она управляет проведением эксперимента, следит за ходом его развития по изменениям температуры, давления и пространственного положения объекта. По окончании эксперимента ЭВМ анализирует результаты и производит в соответствии с полученными данными построение графиков. Каждое утро, когда сотрудник включает машину, та печатает: «Доброе утро, Сузан!»
ЭВМ — это ящичек в комнате для отдыха. У него имеется клавиатура, дисплей, динамик, портативное печатающее устройство и приемник для загрузки гибких дисков с записанной на них информацией. ЭВМ осуществляет хранение корреспонденции, рецептов, финансовой информации, производит опрос учащихся, каждый год 14 апреля подсчитывает сумму налога, подлежащую выплате, включая расходы по содержанию ее самой, является достойным противником в игре в шахматы и наигрывает популярную мелодию. Старомодные друзья обладателя ЭВМ предупреждают его: «Будь осторожен! Машина может свести тебя с ума!»
ЭВМ — это миниатюрная схема, размещенная в бытовом электроприборе. Такая машина, действительно, может приготовить вам завтрак.
Все характеристики, приведенные выше, отражают в первом приближении (но не определяют) четыре класса вычислительных машин: большие ЭВМ, малые ЭВМ, микро-ЭВМ и микро-
2—1393
контроллеры. Несмотря на то что вычислительные машины значительно отличаются друг от друга по габаритам, вычислительным возможностям и стоимости, у них много общих характеристик. Цель настоящей книги состоит в том, чтобы описать принципы построения и программирования ЭВМ любого класса. Большинство рассматриваемых примеров относится к микроЭВМ, исходя из этого выбрано и название книги.
Если говорить об ЭВМ в целом, то она представляет собой значительно более сложную систему, имеющую намного больше уровней детализации, чем, например, автомобиль. Поскольку структура вычислительной системы допускает деление на много уровней, работу ЭВМ понять нетрудно, если проанализировать каждый уровень.
Самым низким уровнем в ЭВМ являются аппаратные, или технические, средства — электронные схемы, из которых состоит машина. Следующий уровень — архитектура, т. е. соединения элементов, а также структура и характеристики ЭВМ, к которым имеет непосредственное отношение пользователь (этот уровень также можно считать аппаратным). Самый высокий и, возможно, самый существенный уровень — программное обеспечение, представляющее собой последовательности команд, или программы, которые управляют работой ЭВМ. Каждый из указанных уровней можно было бы разбить на дополнительные уровни.
В нижеследующих разделах будут рассмотрены аппаратные средства, архитектура и программное обеспечение современных вычислительных систем.
АППАРАТНЫЕ СРЕДСТВА
ЦИФРОВЫХ ВЫЧИСЛИТЕЛЬНЫХ МАШИН
Большинство используемых в настоящее время вычислительных машин являются цифровыми, а не аналоговыми. В аналоговых машинах для представления чисел служат непрерывно изменяющиеся физические величины, такие, как давление, перемещение или напряжение. Простейшими примерами аналоговых машин являются логарифмические линейки и специальные кольцевые калькуляторы. В электронных аналоговых вычислительных машинах расчеты выполняются с помощью электронных схем, рабочие характеристики которых моделируют процесс вычислений. Например, для моделирования операции сложения двух чисел можно использовать схему, выходное напряжение которой представляет собой сумму двух входных напряжений; значение выражения е-1 можно получить, измерив через определенное время напряжение на конденсаторе, который разряжа-
Таблица 1.1. Физические состояния, используемые в различных типах цифровой логики и запоминающих устройств для представления двоичных цифр
Тип устройства
Состояния, соответствующие двоичным цифрам
0
1
Транзисторно-транзи-
0—0,8 В
2,0—5,0 В
сторная логика ;
Оптоэлектронная логика
Отсутствие светового по-
Наличие светового по
тока
тока
Динамическая память
Отсутствие заряда на
Наличие заряда на кон
Программируемые по
конденсаторе
денсаторе
Наличиезахваченных
Отсутствие захваченных
стоянные запоминаю
электронов в ловуш
электронов в ловуш*
щие устройства с воз
ках
ках
можностью стирания Биполярные постоянные
Отсутствие плавкой пе
Наличие плавкой пере
запоминающие устрой
ремычки
мычки
ства
Запоминающие устройст
Отсутствие заряда
Наличие заряда
ва с зарядовой связью Запоминающие устройст
Отсутствие намагничен
Наличие намагниченного
ва на магнитных до
ного домена
домена
менах
Магнитные ленты и
Нет изменения направ
Изменение направления
диски
ления вектора намаг
вектора намагничива
ничивания
ния
Полимерная память
Молекула находится в
Молекула находится в
состоянии, обозначае
состоянии, обозначае
мом как нулевое
мом как единичное
Примечание. В таблице указаны состояний, соответствующие положительной логике. Для отрицательной логики данные состояния необходимо поменять местами.
ется через резистор. Электронные аналоговые вычислительные машины чаще всего используются для моделирования физических систем, описываемых сложными дифференциальными уравнениями.
Отличительной чертой цифровых вычислительных машин является то, что они осуществляют хранение и обработку величин, представляющих собой цифры. Большинство физических явлений, используемых в современных вычислительных устройствах, характеризуются двумя устойчивыми состояниями, каждое из которых соответствует одной из двух цифр. Следовательно, цифровые вычислительные машины производят обработку двоичных цифр, или битов, принимающих значение 0 или 1.
В табл. 1.1 приведены примеры физических явлений, используемых для представления битов в некоторых современных вычислительных устройствах. Большинство этих явлений имеют так называемую область неопределенности между состояниями
2е
нуля и единицы (например, напряжение, равное 1,5 В; световой поток низкой интенсивности; конденсатор, заряженный до небольшой величины, и т. д.). Эта область необходима для того, чтобы иметь возможность задавать, а также достоверно распознавать состояния нуля и единицы. Если границы состояния
X Y X И г
ХИГХИГРис. 1.1. Логические схемы И, ИЛИ, НЕ иРис. 1.1. Логические схемы И, ИЛИ, НЕ и00о
ХИГ
ХИГ
Рис. 1.1. Логические схемы И, ИЛИ, НЕ и
Рис. 1.1. Логические схемы И, ИЛИ, НЕ и
01о
1оо
111
X Y X ИЛИ Y
00о
011
101
111
X НЕ XX НЕ XО1
X НЕ X
X НЕ X
1о
их таблицы истинности.
нуля и единицы находятся слишком близко, то имеющиеся в системе шумы могут легко привести к искажению результата.
Для выполнения операций над битами в вычислительных машинах используется цифровая логика. Основными логическими элементами служат вентили, входными данными для которых являются 1 бит или более, а результат выдается в виде 1 бит. На рис. 1.1 приведены три основных вентиля — И, ИЛИ и НЕ (инвентор)—и их таблицы истинности. С помощью этих вентилей можно реализовать любую логическую функцию. Например, для построения схемы, позволяющей производить сложение 16-разрядных двоичных чисел, потребовалось бы примерно 250 таких вентилей. Вентили представляют собой комбинационные логические элементы — они принимают входные сигналы, выполняют над ними операцию и почти мгновенно (как правило, не более чем через 10 нс) вырабатывают выходной сигнал..
Для хранения логических величин в цифровых схемах используются^ элементы памяти. Основным элементом памяти яв-
ляется R S-триггер (рис. 1.2). На обоих входах триггера в исходном состоянии присутствуют логические нули. Если на вход S подать логическую 1, то сигнал на выходе Q примет значение, равное 1. Так как выход Q соединен с одним из входов нижнего вентиля ИЛИ, значение сигнала Q останется рав-
Рис. 1.2. R — S-триггер:а — схема; б — условное обозначение; в — таблица истинности.Рис. 1.2. R — S-триггер:а — схема; б — условное обозначение; в — таблица истинности.О 0 Предыдущее Qо 1о1011 1 НеопределенноО 0 Предыдущее Qо 1о1011 1 Неопределенноs R Qs R Q88
Рис. 1.2. R — S-триггер:
а — схема; б — условное обозначение; в — таблица истинности.
Рис. 1.2. R — S-триггер:
а — схема; б — условное обозначение; в — таблица истинности.
О 0 Предыдущее Q
о 1о
101
1 1 Неопределенно
О 0 Предыдущее Q
о 1о
101
1 1 Неопределенно
s R Q
s R Q
8
8
ным 1 даже после того, как на входе S триггера вновь установится 0. Для того чтобы на выходе Q появился 0, на вход R триггера необходимо подать логическую 1. Таким образом, триггер можно использовать для хранения 1 бит информации.
На рис. 1.3 приведена таблица истинности более сложного триггера—D-триггера, срабатывающего по положительному фронту импульса синхронизации (тактового импульса). Этот триггер имеет два входа —■ С (вход тактовых импульсов) и D (вход данных). Каждый раз, когда на входе С происходит из-
синхронизсциисинхронизсцииааСDQ
синхронизсции
синхронизсции
а
а
J00
J11
0 х llpeMQ
хПреМ. Q
Рис. 1.3. D-триггер, срабатывающий по положительному фронту импульса синхронизации:
в—условное обозначение; б — таблица истинности.
менение сигнала из 0 в 1, сигнал, присутствующий на входе D, передается на выход Q. Все остальное время значение сигнала на выходе Q не изменяется, а сигнал на входе D не воспринимается. D-триггеры соединяются друг с другом, что позволяет получить регистры, осуществляющие хранение многоразрядных чисел в вычислительных машинах. Например, с помощью 16 D-триггеров можно построить регистр для хранения 16-разряд-
ного двоичного числа. Наряду с триггерами, являющимися наиболее распространенными запоминающими элементами, для построения основной памяти или запоминающих устройств большой емкости используются другие элементы, приведенные' в табл. 1.1. . :
Логические элементы вычислительных машин выпускаются промышленностью в виде интегральных схем (ИС) — электронных схем, выполненных на прямоугольных пластинках кремния с длиной стороны нс более 1 см и реализующих определенную функцию. Интегральные схемы, содержащие несколько логических вентилей или триггеров, называются схемами малой степени интеграции (МИС).В современных вычислительных системах такие схемы в основном используются для реализации всевозможных нерегулярных функций управления. Болес сложные и регулярные функции управления и обработки данных можно выполнить с помощью схем средней степени интеграции (СИС). Такие схемы содержат от 10 до 100 вентилей и триггеров, соединенных друг с другом, и предназначены для реализации хорошо структурированных и часто используемых функций, таких, как сложение двух 4-разрядных двоичных чисел, запоминание 8-разрядных чисел или выбор одного из восьми входных битов.
Функции систем более высокого уровня, для реализации которых требуется более 100 вентилей, выполняются с помощью схем большой и сверхбольшой степени интеграции (БИС и СБИС), Граница между схемами двух последних типов (имеется в виду число вентилей, входящих в схему) не является четкой. Иногда границей считают 100 000 компонентов (число транзисторов и диодов); при этом для построения одного логического вентиля может потребоваться пять компонентов, а одного динамического элемента памяти — только один. С функциональной точки зрения наиболее простой схемой, относящейся к БИС, является регистровое запоминающее устройство емкостью 4x4 бит. Если же говорить о более сложных схемах данного вида, то к ним следует отнести запоминающие устройства емкостью 64К бит1), 8- и 16-разрядные микропроцессоры,, устройства управления памятью, схемы последовательной передачи данных и контроллеры периферийных устройств. Запоминающие устройства емкостью 128К бит и более, сложные 16-разрядные микропроцессоры и микро-ЭВМ, а также 32-разрядные
^ Обычно буквы К и М служат для обозначения величин, равных 1000 и 1 000 000 соответственно. Однако при рассмотрении памяти вычислительных машин, по рекомендации Американского национального института стандартов, эти буквы обозначают величины, равные 210 (1024) и 220 (1 048 576). *
Предупреждение. Если вы поступили на работу программистом и вам положили оклад 2 5 К долл., не рассчитывайте получить сумму 25 600 долл.
процессоры (которые уже нельзя называть микропроцессорами) следует считать схемами сверхбольшой степени интеграции.
Рис. 1.4 позволяет получить представление о физических размерах цифровых интегральных схем, а также о корпусах, в которых они размещаются. Для схем более высокой степени ин-
Рис. 1.4. Печатная плата с установленными на ней корпусами ИС:
о — корпус с 16 выводами; б — корпусы с 20 выводами; в — корпусы с 40 выводами. (С разрешения Cromemco.)
теграции, как правило, требуются и корпусы большего размера. Однако некоторые очень большие схемы с регулярной структурой, такие, как устройства памяти емкостью 64К бит, могут быть размещены и в маленьком корпусе. Для построения законченной вычислительной системы обычно требуется от 1 до 100 000 корпусов интегральных схем, поэтому способы их компоновки в системе могут сильно различаться.
БАЗОВАЯ ОРГАНИЗАЦИЯ ЭВМ
Вычислительная система состоит из трех основных подсистем '(рис. 1.5): процессора, памяти и устройств ввода-вывода. «Сердцем» вычислительной машины является процессор (или центральный процессор — ЦП). Как показано на рис. 1.6, в состав простого процессора входят схемы управления выборкой и выполнением команд; арифметическо-логическое устройство
(АЛУ), позволяющее выполнять операции над данными; регистры, осуществляющие хранение признаков состояния, а также небольшого числа данных. Кроме того, процессор включает
Рис. 1.5. Структурная схема типичной ЭВМ.
в себя схемы интерфейса, которые служат для управления подсистемами памяти и ввода-вывода и для осуществления с ними связи.
Структурная схема, приведенная на рис. 1.6, не позволяет составить полное представление о работе процессора. Иногда
Рис. 1.6. Структурная схема процессора.
прибегают к описанию процессора на уровне регистровых пе- редач. В этом случае указывают имена и функции регистров и узлов, реализующих операции, рассматриваются функционирование и взаимодействие этих узлов при выполнении каждой команды. Начиная с гл. 5 для описания различных процессоров
ца уровне регистровых передач будет использоваться язык про- Враммирования Паскаль.
д Память (или основная память) вычислительной машины включает в себя устройство, обеспечивающее хранение команд > данных. Она связана с процессором с помощью шины памяти ^(рис. 1.5). Шина представляет собой обычный набор проводов |ли любую другую физическую среду, через которую передается информация. Память состоит из большого числа ячеек, каждая из которых предназначена для хранения b-разрядной величины. Каждая ячейка памяти однозначно определяется с помощью двоичного числа, называемого адресом. ' Если имеется и ячеек памяти, то адреса могут принимать значения от 0 до п— 1.
Основным признаком, по которому основную память можно ртличать от других видов памяти большой емкости, является Возможность произвольной выборки ее ячеек. Другими словами, Сремя обращения процессора ко всем ячейкам такой памяти Одинаково. Память с произвольной выборкой подобна стойке ^абонентских ящиков на почтовом отделении. В любой из этих Йщиков можно поместить корреспонденцию с одинаковой легкостью. Для сравнения укажем, что почтальон при доставке Писем на дом пользуется методом последовательной выборки, Заключающимся в том, что обращение к ячейкам (почтовым ящикам адресатов) производится последовательно по мере движения почтальона по маршруту. Примером памяти вычислительных машин, обеспечивающей последовательную выборку, являются магнитные ленты.
На рис. 1.7 показано, как осуществляется обращение процессора к основной памяти в небольшой вычислительной системе. Память представляет собой массив из п ячеек по b разрядов в каждой. Для того чтобы произвести считывание данных, записанных в ячейке памяти по адресу X, процессор выдает число X на адресную шину и вырабатывает сигнал управления считыванием. В ответ на это из памяти на шину данных поступает содержимое ячейки с адресом X. Чтобы записать величину V в память по адресу X, процессор подает число X на адресную шину, а число V — на шину данных и вырабатывает сигнал записи в память. Величина V оказывается записанной в заданную ячейку. Если теперь произвести считывание содержимого ячейки с адресом X, мы вновь получим величину V.
Подсистема ввода-вывода включает в себя внешние (периферийные) устройства, с помощью которых осуществляются связь с внешней по отношению к ЭВМ средой, наблюдение за ней и управление ею. Внешними устройствами являются терминалы, печатающие устройства, устройства связи, механические .датчики и исполнительные механизмы. К подсистеме ввода-вы-
вода относятся также устройства памяти большой емкости — это магнитные ленты и диски. Данные устройства служат для хранения такой информации, которую не требуется постоянно иметь в основной памяти, например системных программ и текстовых файлов. Устройства памяти большой емкости имеются нс у всех вычислительных машин, однако все ЭВМ имеют по крайней мере одно периферийное устройство, так как, по опре-
Основная память
Рис. 1.7. Основная память типичной ЭВМ.
делению, периферийное оборудование является единственным средством связи ЭВМ с внешней средой.
Процессор осуществляет запись во внешние устройства и считывание из них информации с помощью команд ввода-вывода, выполнение которых приводит к подаче на шину ввода-вывода как данных, так и различных команд. Во многих ЭВМ память и внешние устройства имеют общую шину. Болес того, во многих вычислительных машинах связь с внешними устройствами осуществляется с использованием регистров, операции с которыми выполняются так, как если бы они являлись ячейками памяти. В таких системах аппаратные средства не делают никаких различий между обращением к основной памяти и обращением к внешним устройствам (соответствующие примеры будут приведены в гл. 10).
В простых вычислительных системах не существует прямой связи внешних устройств с основной памятью. Поэтому передача данных между ними может быть выполнена только с помощью процессора, который производит считывание данных из: внешних устройств и запись этих данных в основную память, и наоборот. Однако в системах, где требуется передача данных с
более высокой скоростью, предусмотрена возможность работы в режиме прямого доступа к памяти (ПДП), т. е. имеется канал связи между специальным контроллером периферийного оборудования и памятью, позволяющий внешним устройствам производить запись в память и считывание из нее данных без участия процессора. (Этот режим работы будет рассмотрен в гл. 11).
ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ
ВЫЧИСЛИТЕЛЬНЫХ СИСТЕМ
Программное обеспечение ЭВМ состоит из команд и данных, обрабатываемых с помощью аппаратных средств с целью выполнения полезной работы. Последовательность команд, использованных в вычислительной машине, называется программой. Данные, которыми оперирует программа, в зависимости от их характера и объема носят название базовых данных, файлов, входных данных или просто данных.
Наиболее простыми командами, которые выполняет вычислительная машина, являются команды машинного языка, т. е. такие команды, которые непосредственно воспринимаются аппаратными средствами. Команды машинного языка представляют собой последовательности двоичных цифр, записанные в Памяти машины, причем одна команда часто занимает одну ячейку памяти. Процессор осуществляет выборку машинных команд из памяти одну за другой и их выполнение. С помощью машинных команд реализуют такие простые операции, как «Прибавить 1 к содержимому регистра А», «Записать содержимое регистра А в ячейку памяти с адресом 15», «Прибавить содержимое ячейки памяти с адресом 35 к содержимому регистра В» или «Переход к выполнению последовательности команд, первая из которых находится в ячейке памяти с адресом 207, если содержимое регистра В равно нулю».
Так как человеку трудно читать и распознавать последовательности двоичных цифр, или битов, программы машинного языка сначала записывают на языке ассемблера, а затем с помощью программы ассемблер транслируют в последовательности битов. На языке ассемблера машинные команды имеют вид мнемонических обозначений (имен), а адреса памяти и другие константы могут быть представлены не последовательностью битов, а символами.
Например, машинную команду, с помощью которой производится прибавление содержимого ячейки памяти с адресом 35 к содержимому регистра В, можно записать в виде цепочки битов следующим образом: 11011011 00100011, где 11011011—код операции команды «Прибавить содержимое ячейки памяти к содержимому регистра В», а 00100011 —8-битовый двоичный код
числа 35. На языке ассемблера эта же команда может быть за* писана как ADDB VAR, где ADDB — мнемоническая запись, команды, a VAR— символьная 8-битовая константа, значение которой, равное 35, задастся в программе. Ячейка памяти с ад* рссом 35 содержит переменную (названную VAR), которая при* бавляется к содержимому регистра В. Ассемблер осуществляет трансляцию команды ADDB VAR на языке ассемблера в после* доватсльность битов 11011011 001 000 11.
Программное обеспечение многих вычислительных машин представляет собой программы, написанные на языке ассемблера. Этот язык используется преимущественно для малых ЭВМ,. а также для записи часто вызываемых программных модулей’ больших вычислительных машин. Однако для выполнения операций, которые описываются всего одной или несколькими строками естественного языка, например: «Установить W равным W плюс X минус У, деленными на Z» или «Повторять следую* щую последовательность команд до тех пор, пока X не примет значение меньше нуля или У нс будет равно Z» требуется ис* пользовать достаточно большое число команд языка ассемблера.
Исследования показали, что время написания и отладки программы, а также трудность ее понимания и обслуживания про* порциональны числу команд и отчасти сложности каждой команды. Поэтому большинство программ записывают на языках высокого уровня, которые позволяют выполнять наиболее распространенные операции, такие, как вычисление выражений,, повторение циклов, присвоение значений и условные действия,, используя в каждом случае только один оператор данного языкам высокого уровня. Более того, способы записи программ, принятые в структурных языках высокого уровня, например в Паскале, облегчают разработку, понимание и обслуживание программ.
Лишь некоторые типы вычислительных машин могут непосредственно воспринимать язык высокого уровня. Поэтому ДЛЯ' трансляции программы, написанной на языке высокого уровня,, в последовательность машинных команд, с помощью которых, решается поставленная задача, необходим компилятор. Поскольку для каждого оператора высокого уровня может потребоваться от пяти до десяти машинных команд, совершенно очевидно, что по сравнению с языком ассемблера использование- языка высокого уровня для составления программ приводит к, сокращению времени и стоимости их разработки.
Однако имеются и некоторые аргументы в пользу программирования на языке ассемблера. Так как язык ассемблера — это в сущности отображение машинного языка, теоретически: всегда существует возможность написать такую программу на» языке ассемблера, которая была бы не менее компактной и эф-
фективной, чем программа на машинном языке, полученная с помощью любого компилятора. Компилированная программа в зависимости от типа компилятора, вычислительной машины и назначения может быть на 0—300% длиннее и медленнее оптимальной программы на машинном языке, но в то же время она может быть значительно лучше программы на машинном языке, написанной программистом средней квалификации.
Обычно при оценке системы предпочтение отдастся низкой стоимости разработки программ на языках высокого уровня и простоте их обслуживания, а нс эффективности программ. В большинстве случаев стоимость дополнительной памяти, необходимой для хранения более длинных программ, меньше заработной платы дополнительного числа программистов. Это особенно заметно при разработке программ большого объема, поскольку для написания программы вдвое большей длины может потребоваться в четыре раза больше программистов. Таким образом, имеет место нелинейное увеличение времени, затрачиваемого на взаимодействие исполнителей, по мере увеличения объема программ.
Скорость выполнения программ, составленных на языках высокого уровня, обычно не является неразрешимой проблемой. Если значение этого параметра окажется слишком низким, то в большинстве случаев его можно повысить, нс изменяя большей части программы. Обычно на реализацию 10% объема программы расходуется до 90% общего времени се выполнения. Критические участки программы с целью оптимизации могут быть переписаны либо на исходном языке высокого уровня, либо на языке ассемблера.
В связи с тем что в программе машинные команды ЭВМ присутствуют в закодированном виде, процесс написания команд на любом языке называется кодированием, а записанная команда — кодом. Кодирование является лишь небольшой составной частью программирования, которое включает формулировку требований, предъявляемых к программе, составление проекта программы, разработку документации, кодирование и отладку программ.
Ассемблеры и компиляторы — это отнюдь не единственные программные средства, с которыми имеет дело программист. Другими средствами, полезными для разработки программ, являются интерпретаторы, эмуляторы и отладчики. Интерпретатор, как и компилятор, служит для обработки программ, написанных на языках высокого уровня. Однако в отличие от компилятора интерпретатор реально выполняет исходную программу, написанную на языке высокого уровня, по одному оператору, а не транслирует каждый ее оператор в последовательность машинных команд, реализуемых позднее при прогоне программы.
Эмулятор представляет собой программу, позволяющую моделировать отдельные машинные команды одной ЭВМ на другой вычислительной машине, которая, как правило, отличается от первой. Эмулятор часто используется для проверки программ, предназначенных для прогона на процессоре, до построения процессора. Специальный отладчик выполняет программу по •одной или несколько команд за один шаг, что позволяет программисту проверять результат выполнения отдельных фрагментов программы и обнаруживать ошибки.
Редактор текста используется для ввода текста в вычислительную машину общего назначения и его редактирования, причем независимо от того, является ли данный текст докладом или программой, составленной для ЭВМ. Кроме того, имеются ►средства задания формата текста. Редакторы и средства задания формата текста относятся к области вычислений, которая называется текстовой обработкой.
В средних и больших вычислительных машинах взаимосвязанные программы выполняются под управлением операционных систем. Операционные системы устанавливают порядок выполнения программ, управляют использованием устройств ввода-вывода и реализуют ряд функций по обслуживанию программ. Программами и текстами, записанными на дисках и в других запоминающих устройствах большой емкости, управляют с помощью файловой системы, которая представляет собой набор программ, предназначенных для чтения и записи такого вида информации, а также для образования из нее файлов. В большинстве вычислительных машин файловые системы входят в состав операционных систем. Даже малые ЭВМ, у которых нет запоминающих устройств большой емкости или файловой системы, обязательно имеют простую операционную систему, обеспечивающую по крайней мере управление входными данными и прием команд из внешней среды.
АЛГОРИТМЫ
«ЭВМ — глупые машины: они не будут ничего делать до тех пор, пока им не прикажут, а при получении приказа строго следуют всем указаниям, даже если последние бессмысленны». Эта известная фраза довольно точно отражает вычислительные способности ЭВМ. Для того чтобы вычислительная машина смогла решить какую-либо задачу, следует сформировать определенную последовательность команд называемую алгоритмом.
Последовательность команд, должна удовлетворять двум важным условиям. Во-первых, каждая команда последовательности должна входить в основной набор команд вычислительной машины, реализующей алгоритм. Операции, соответствую-
щие этому набору команд, называются простыми. Так, алгоритм умножения числа 119 на 102 может быть представлен всего одной командой, если операция умножения является для нее простой. Данный алгоритм может также состоять из нескольких операций сложения и сдвига, если указанные операции являются простыми для машины. В тех случаях, когда ЭВМ реализует только такие простые операции, как сложение и последовательный счет, умножение состоит из 102 последовательных операций сложения числа 119.
Во-вторых, алгоритм должен приводить к получению результата за конечное число шагов. Это означает не только то, что число команд, используемых при реализации алгоритма, должно быть конечным, но и что любые команды, с помощью которых осуществляется подсчет, повторение операций или выявление ошибок, также должны использоваться конечное число раз. Например, последовательность команд, предназначенную для вычисления всех простых чисел, нельзя назвать алгоритмом, так как количество простых чисел бесконечно.
Последовательность команд может быть бесконечной даже в том случае, если поставленная задача имеет определенное решение. Рассмотрим последовательность команд, предназначенную для деления положительного числа X на 5:
Установить Q равным 0.
Установить X равным X—5, a Q равным Q+L
Закончить выполнение операций, если X находится в диапазоне между 0 и 4; при этом Q является частным, а X — остатком. В противном случае возврат к шагу 2.
Данная последовательность вполне подходит для решения задачи, если первоначальное значение X равно 5 или большей величине. Однако, если первоначальное значение X находится между 0 и 4, эта последовательность является бесконечной. При выполнении первой проверки на шаге 3 X будет иметь отрицательное значение, которое при дальнейшем последовательном повторении шагов 2 и 3 становится все более отрицательным. Рассмотренный случай является примером типичной грубой ошибки составления алгоритма, когда последовательность команд, позволяющая в большинстве случаев решить задачу, в особых ситуациях оказывается непригодной из-за неожиданных результатов, получаемых при проверке условий.
В рассмотренном примере можно было изменить шаг 3 и проверять только то, является ли значение X меньше или равно 4. Тогда последовательность была бы конечной, но приводила бы к получению неправильного результата для значений X, лежащих в диапазоне между 0 и 4. Приведенный ниже алгоритм реализуется за конечное число шагов и позволяет полу-
чить правильный результат для любых положительных значений X;
Установить Q равным 0.
Закончить выполнение операций, если X находится в диапазоне между 0 и 4; при этом Q является частным, а X— остатком. В противном случае установить X равным X—5, a Q равным Q+ 1..
Возврат к шагу 2.
Даже правильные алгоритмы могут быть усовершенствованы. Для рассмотренного выше алгоритма предполагалось, что используются только положительные значения X. Если же первоначальное значение X является отрицательным, то последовательность команд будет бесконечной. В алгоритмах, обеспечивающих надежное выполнение программ, всегда предусматривается проверка, того, находятся ли входные данные в разрешенном диапазоне. Составление таких алгоритмов является одним из важнейших аспектов программирования.
ТИПЫ ДАННЫХ ЭВМ
Может создаться впечатление, что вычислительные машины производят операции только над числами. В действительности они обрабатывают данные различных типов. Можно даже сказать, что в настоящее время входные и выходные данные ЭВМ в большинстве случаев не являются числовыми.
Напомним, что основной единицей информации в ЭВМ служит бит, принимающий значение 0 или 1. Очевидно, что 1-битовые данные не подходят для выполнения числовых расчетов, так как позволяют осуществлять последовательный счет толькс от 0 до 1. Однако бит является очень полезным типом данных если использовать его значения (0 и 1) для представления двуэ логических величин — «истинно» и «ложно». Полученный такта образом тип данных называется логическим или булевым i честь ученого Джорджа Буля, который разработал математиче скую систему, являющуюся основой алгебры логики, исполь зуемой при проектировании цифровых логических систем. Логи ческий тип данных широко используется в программах для раз личных целей, например для сохранения результатов операци] сравнения, указания особых случаев и вообще для определени двух возможных исходов или условий выполнения операций.
Последовательность, состоящая из двух или большего числ битов, может использоваться для представления более чем дву величин или условий. Если число битов в последовательност равно п, то имеется возможность составить 2П различных kom6i наций их значений. Например, совокупность 4 бит позволяет п<
Таблица 1.2. Четырехбитовые комбинации и соответствующие им значения
Комбкаадж бит
Числа беа анака
Число со анакам
Цвет
Герод
0000
0
-0
Черный
Сент-Луис
0001
1
н
Коричневый
Майами
0010
2
Н2
Красный
Чикаго
ООП
3
из
Оранжевый
Сан-Франциско
0100
4
-4
Желтый
Детройт
0101
5
-5
Зеленый
Нью-Йорк
оно
6
-6
Голубой
Бостон
0111
7
-7
Фиолетовый
Хьюстон
1000
8
—8
Серый
Денвер
1001
9
-7
Белый
Мемфис
1010
10
-6
Серебряный
Портленд
1011
11
-5
Золотой
Милуоки
1100
12
Бронзовый
Сиэтл
1101
13
—з
Индиго
Джуно
1110
14
-2
Розовый
Гонолулу
1111
15
-1
Терракотовый
Альбукерке
лучить 16 комбинаций, каждой из которых можно поставить в соответствие определенное значение (табл. 1.2). В гл. 4 будут рассмотрены различные способы присвоения числовых значений комбинациям битов. Однако, как следует из двух последних столбцов таблицы, группе битов, можно присваивать и нечисловое значение. Обычно для каждой комбинации битов числовое имя задают даже в том случае, если она служит для представления нечисловой величины. Например, анализируя табл. 1.2, можно сказать, что зеленый цвет представляется числом 5.
Последовательность из 8 бит обычно называют байтом. Это понятие впервые было введено в употребление фирмой IBM еще на заре развития вычислительной техники. Цепочку из 4 бит именуют полубайтом.
Совокупность битов, которыми вычислительная машина оперирует одновременно, называется словом. Длина Слова у различных ЭВМ обычно находится в пределах 4—64 бит, но может иметь и большую величину. Для значительного числа мини- и микро-ЭВМ термин «слово» обозначает 16-битовые цепочки. Последовательность из 32 бит в таких случаях именуют двойным словом. Применительно к большим вычислительным машинам (например, к системе 370 фирмы IBM) под словом понимают 32-битовую последовательность, а под двойным словом и полусловом— соответственно цепочки из 64 и 16 бит. В данной книге используется стандартная терминология, принятая для микро- ЭВМ: полубайт — 4 бит; байт — 8 бит; слово—16 бит; двойное слово — 32 бит; слово четырехкратной длины — 64 бит.
3—1393
Биты, полубайты, байты и слова являются такими типам» данных, которые могут быть легко классифицированы, так как для их^ хранения требуется различный объем памяти вычислительной машины. Существуют, однако, и другие типы данных, классификация которых производится не по объему занимаемой ими памяти, а по тому, как они интерпретируются и используются аппаратными и программными средствами ЭВМ. Например, для микро-ЭВМ с помощью 16-битового слова можно определить четыре следующих типа данных:
Целые числа без знака. Слово представляет собой целое число без знака, значение которого находится в пределах О— 65535.
Целые числа со знаком. Слово представляет собой целое число со знаком, значение которого находится в пределах —32 768 4-+32 767.
Адрес. Слово представляет собой адрес ячейки памяти, объем которой не превышает 65 536 байт.
Логический массив. Слово содержит 16 независимых флажков.
Несмотря на то что запись всех четырех типов данных в память ничем не отличается, их обработка аппаратными и программными средствами проводится по-разному. Например, выполнение арифметической операции над целыми числами бе» знака приводит к переполнению, если результат меньше 0 ил» больше 65535. Если же операция выполняется над целыми числами со знаком, то переполнение происходит, когда результат меньше —32 768 или больше 32 767.
Для той же микро-ЭВМ можно определить три типа данных, задаваемых с помощью 32-битового (двойного) слова:
Длинные целые числа со знаком. Слово двойной длины представляет собой целое число, значение которого находится в» пределах —2 147 483 648ч-+2 147 483 647.
Числа с плавающей точкой. Слово двойной длины есть, число в пределах —1.7 • 1038ч- + 1.7-1038.
Длинный адрес. 24 младших бита слова двойной длины служат в качестве адреса памяти, в то время как восемь старших битов не используются.
КЛАССИФИКАЦИЯ
ВЫЧИСЛИТЕЛЬНЫХ МАШИН
Рассмотрение вычислительных машин можно проводить с разных точек зрения, а для их классификации использовать, различные критерии. В данном разделе приведена классификация, которой мы будем придерживаться на протяжении всей книги.
Если исходить из размеров системы, то вычислительные машины подразделяются на сверхбольшие, большие, средние, малые и микро-ЭВМ. Дадим краткое определение каждого вида ЭВМ.
Сверхбольшие ЭВМ. Вычислительные машины очень высокой производительности, предназначенные для выполнения сложных научных расчетов; их стоимость исчисляется миллионами долларов. Примерами таких машин являются CRAY-I, ILLIAC IV.
Большие ЭВМ. Большие вычислительные системы общего назначения высокой производительности; их стоимость составляет более 1 млн. долл. Это такие машины, как CYBER 76, IBM 3033.
Средние ЭВМ. Вычислительные машины общего назначения, которые по производительности и стоимости занимают промежуточное место между большими и малыми ЭВМ. К машинам этого типа относится, например, ЭВМ VAX-11/780.
Малые ЭВМ (или мини-ЭВМ^. Вычислительные машины общего назначения, используемые главным образом для специальных целей; их стоимость находится в пределах 20—200 тыс. долл. (ЭВМ НР300, PDP-11/70).
Микро-ЭВМ. Вычислительные машины, ориентированные на решение специальных задач. Процессорами таких ЭВМ служат микропроцессоры. Стоимость микро-ЭВМ составляет меньше 20 тыс. долл. Примерами таких машин являются Apple II, TRS-80, PDP-11/23.
Прежде чем приступить к дальнейшему изложению материала, необходимо дать определение еще четырем устройствам, в названии которых присутствует приставка «микро».
Микропроцессор. Законченный процессор, выполненный на основе одной или нескольких БИС, который предназначен для построения микро-ЭВМ (определение микро-ЭВМ см. выше). Примерами микропроцессоров могут служить Z8000, М6809, М68000.
Однокристальная микро-ЭВМ. Вычислительное устройство, включающее в себя процессор, память и систему ввода-вывода и реализованное в виде одной БИС (Z8, MCS-48, М6801).
Микроконтроллер. Вычислительное устройство, выполненное в виде одной или нескольких БИС, которое содержит процессор, память и систему ввода-вывода и имеет узкоспециальное применение. К этому типу устройств относятся процессоры, используемые в ультразвуковых печах, автомобилях, электронных игрушках.
Процессоры с микропрограммным управлением. Особый тип процессоров, реализующих каждую машинную команду в виде простейших микрокоманд. (Микропрограммированием называ* 3*
ется процесс написания последовательностей микрокоманд, а не программирование микропроцессоров.) К микропрограммируе- мым процессорам относятся М68000, LSI-11/2, Z8.
Приведенные определения типов вычислительных машин по ряду причин не являются точными. Классификацию, основанную на объеме аппаратных средств или производительности, нельзя считать устоявшейся, поскольку технология изготовления ЭВМ интенсивно развивается. Так, многие современные малые ЭВМ превосходят по своим возможностям большие ЭВМ, выпускавшиеся 10 лет назад.
Классификация, принятая исходя из назначения ЭВМ, также не является однозначной, так как часто для одних и тех же целей используются вычислительные машины, отличающиеся друг от друга по размерам и стоимости. Причем использование той или иной машины зависит только от сложности решаемых задач и предъявляемых требований. Например, текстовая обработка отдельными пользователями может производиться на микро-ЭВМ, а в крупных организациях для тех же целей применяют большие вычислительные машины, обслуживающие многих пользователей.
Единственной надежной характеристикой для проведения классификации вычислительных машин является стоимость. Достижения в области технологии позволяют со временем создавать ЭВМ большей производительности при сохранении прежней стоимости и таким образом удовлетворять потребности пользователей в более эффективных вычислительных машинах определенного типа.
В связи с тем что классификация ЭВМ не является точной, одну и ту же вычислительную машину иногда относят к разным категориям. Общие принципы построения и программирования ЭВМ, которые изложены в части II, применимы к вычислительным машинам любого класса.
ЛИТЕРАТУРА
В настоящее время имеется целый ряд книг, в которых подробно рассматриваются аппаратные средства вычислительных машин: устройства на уровне вентилей; стандартные блоки, построенные на основе СИС и БИС; системы памяти, устройства и интерфейсы ввода-вывода, а также вопросы проектирования вычислительных систем. Наиболее известными среди этих книг являются: Мано М. М., Digital Logic and Computer Design, Prentice-Hall, 1979; Hamacher V. C., Vranesic Z. G., Zaky S. G., Computer Organization, McGraw-Hill, 1978 и Peatman J., Microcomputer-Based Design, McGraw-Hill, 1978.
Еще большее число изданий посвящено вопросам архитектуры вычислительных машин. Такие книги, как Introduction to Computer Arcnitecture, ed. by H. Stone, SRA, 1980 и Bell C. G., Newell A., Siewiorek D. P., Principles of Computer Structures, McGraw-Hill, 1980, знакомят с современными принципами организации аппаратных средств и программного обеспечения. Разработчикам аппаратной части ЭВМ особенно полезен сборник статей Bell С. G., Mud-
*i ge J. С., McNamara J. E., Computer Engineering, Digital Press, 1978, в котором приведено описание практически всех моделей ЭВМ, выпускаемых фирмой Digital Equipment Corporation. Здесь же дается большое число практических советов и семь различных способов классификации вычислительных машин.
В настоящее время стоимость программного обеспечения ЭВМ превышает стоимость аппаратных средств. Существует достаточно много вопросов, свя- ванных с программным обеспечением ЭВМ, которые заслуживают дополнительного изучения. Мы заострим внимание на этих вопросах по мере того, как они будут встречаться в последующих главах.
Данная книга не является руководством по программированию. В ней рассмотрены только некоторые приемы составления программ н правила, которыми при этом следует руководствоваться, но не обсужден процесс написания программ. Мы надеемся, что большинство читателей уже имеют определенные навыки программирования на языках высокого уровня. Если же такие навыки отсутствуют, мы рекомендуем ознакомиться со следующими книгами. В работе Schneider, Weingard, Perlman, An Introduction to Programming and Problem Solving With Pascal, Wiley, 1978 можно найти подробное изложение того, как нужно составлять алгоритмы. В указанной книге, как и в издании Grogono Р., Programming in PASCAL, Addison-Wesley, 1978, шаг за шагом рассмотрен процесс создания многих программ на языке Паскаль. Аналогичные вопросы применительно к языку RATFOR, являющемуся одним из вариантов языка Фортран, рассмотрены в книге Kernighan В. W., Plauger Р. J., Software Tools, Addison-Wesley, 1976. Кроме того, в данной работе описаны некоторые программные средства, используемые для получения программ. Даже если вы считаете, что умеете составлять программы, полезно изучить книгу Kernighan В. W., Plauger Р. J., The Elements of Programming Style, McGraw- Hill, 1978, посвященную искусству создания хороших программ.
22ЯЗЫК ПРОГРАММИРОВАНИЯ ПАСКАЛЬ
2
2
Паскаль — это «структурный» язык высокого уровня, который позволяет естественным образом определять данные и писать программы с использованием принципа иерархической организации. Наряду с широким использованием языка Паскаль для программирования научных, технических и экономических задач для больших ЭВМ его применяют при работе с микро- и мини-ЭВМ. Для всех основных типов микро-ЭВМ разработаны компиляторы с языка Паскаль; несколько фирм, производящих микро-ЭВМ, использовали Паскаль при создании программного обеспечения. Фирма Western Digital выпускает микро-ЭВМ, в которой в качестве машинного языка применен упрощенный Паскаль, названный «Р-кодом».
Н. Вирт, создатель языка Паскаль, назвал его так в честь французского математика и философа Блеза Паскаля. Однако не философские концепции Паскаля побудили Н. Вирта дать языку такое название. В письме в журнал Electronics) он писал:
«В действительности я не способен полностью понять философию Паскаля и оценить его религиозную экзальтацию. Однако Паскаль был первым или, быть может, одним из первых, кто изобрел и сконструировал устройство, которое теперь мы относим к классу цифровых вычислительных машин. Он сделал это в возрасте 16 лет, побуждаемый желанием помочь своему отцу, который был сборщиком налогов и выполнял многочисленные и утомительные расчеты».
Настоящая глава содержит краткое описание языка программирования Паскаль. Она служит введением в Паскаль для читателей, не знакомых с этим языком, и обзором для тех, кто с ним работал.
Описание Паскаля, приведенное в этой главе, далеко не полное. Здесь рассматриваются основные элементы языка Паскаль.
Некоторые вопросы, связанные с этим языком, обсуждаются в других главах. Так, массивам посвящены разд. 3.1 и 3.2 (гл. 3), а процедурам и функциям — разд. 9.1 и 9.2 (гл. 9). Некоторые средства Паскаля не затронуты вовсе, например множественный тип, обработка файлов, записи, указатели, динамические структуры данных, форматный ввод-вывод и вопросы зависимости языка от средств реализации. Средства реализации языка Паскаль для конкретной системы состоят из компилятора и программных модулей, используемых во время выполнения программы.
Тем не менее представленное здесь подмножество языка Паскаль полностью соответствует рассматриваемым в следующих главах алгоритмическим описаниям структур данных, программам, написанным на языке ассемблера, и наборам команд ЭВМ. Рассматриваемое подмножество языка Паскаль достаточно для написания реальных прикладных программ. Однако читатели, которые намереваются составлять программы на Паскале и решать с их помощью задачи на ЭВМ, должны воспользоваться руководством по соответствующему компилятору и, возможно, одной из книг по языку Паскаль, список которых приведен в конце данной главы.
ОБЩИЕ ПРЕДСТАВЛЕНИЯ
Паскаль является «структурным» языком благодаря следующим трем ключевым элементам: описаниям, блочной структуре программ и процедурному аппарату. В описаниях (объявлениях) программист должен давать определенную информацию для компилятора относительно структуры программы. В них указываются имена и типы всех переменных, которые будут использованы в программе, а также имена всех меток, на которые будут во время выполнения программы осуществляться переходы с помощью оператора GOTO. Кроме того, описания позволяют хорошему программисту предоставлять компилятору дополнительную информацию и улучшать удобочитаемость программы следующими способами: посредством использования идентификаторов, которые отображают константы программы; Путем ограничения области значений переменных, что позволяет автоматически обнаруживать некоторые ошибки; благодаря явному определению структур данных, которое является понятным и естественным для программиста и воспринимается компилятором.
На рис. 2.1 дан пример блочной структуры программы, написанной на языке Паскаль. Операторы определяют действия, выполняемые в программе. В Паскале используются как простые, так и структурные операторы. Простой оператор задает одно
BEGIN
Простой оператор
Структурный оператор
Простой оператор
Структурный оператор BEGIN
Простой оператор
Простой оператор
Структурный оператор- Простой оператор
Структурный оператор
BEGIN
Простойопер,
Простои опер.
END
END
Простой оператор
Простой оператор
END
Рис. 2.1. Пример блочной структуры программы на языке Паскаль.
действие. Например, оператор присваивания х:=(3+у)/7 вычисляет значение выражения (3+у)/7 и присваивает это значение переменной х. Структурный оператор содержит один или несколько других операторов и осуществляет управление ими при соблюдении строго определенных правил. Наиболее важным
структурным оператором является составной оператор, который представляет собой последовательность операторов, заключенных между зарезервированными словами BEGIN и END. Другим примером структурного оператора является оператор FOR,
ПРОГРАММА
Описания
ПРОЦЕДУРА
Описания
ПРОЦЕДУРА
Описания Операторы
ФУНКЦИЯ Описания Операторы
Операторы
ФУНКЦИЯ Описания
ПРОЦЕДУРА Описания Операторы
Операторы
ФУНКЦИЯ Описания
Операторы
ПРОЦЕДУРА Описания Операторы
Операторы
Рис. 2.2. Пример структуры программы на языке Паскаль, содержащей процедуры и функции.
который обеспечивает выполнение некоторого оператора заранее определенное количество раз.
При рассмотрении принципа блочной организации программы надо иметь в виду, что структурный оператор может управлять любым другим оператором, в частности структурным. Если изобразить каждый структурный оператор в виде блока, то программа, представленная на рис. 2.1, будет выглядеть как неко-
торое множество вложенных блоков. Блочная структура программы на языке Паскаль позволяет наиболее естественным образом решать задачи путем использования условного опера-
PROGRAM AddingMachine (input,output): (Эта программа моделирует 13-клавишный сумматор. Клавиши имеют следующее назначение:
0-9- цифры. Цифры числа вводятся слева направо, по одной;
С - сброс. Установка накапливаемой суммы в состояние О;
+ - плюс. Прибавление текучего числа к накапливаемой сумме; S -стоп. Останов машины (и программы).
Нажатие клавиш моделируется путем ввода литер. Текучее число и накопленная сумма печатаются после выполнения каждой из операций С, + или S. Если вводится недопустимая литера, то текучее число и накопленная сумма сбрасываются}
VAR sum, number : integer; charin : char;
PROCEDURE PrintNums; {Print current number and sum so far.) BEGIN writein (number,sum) END;
PROCEDURE InitNums;{Clear current number and sum and print them.}
BEGIN sum := 0; number := 0; PrintNums; END;
FUNCTION DigitVal (c : char) : integer; {Evaluate a digit from 0 to 9.) BEGIN
{The value of a digit is the value of its numeric character code minus the value of zero's numeric character code.)
DigitVal := ord(c) - ord('O');
END;
BEGIN {Main Program}
InitNums,’ read(charln); {Initialize variables; get first char.} WHILE charin <> 'S' DO {Read a character at a time.}
BEGIN
IF (charIn>='0') AND (charln<='9') THEN {if charin is a digit... multiply current number by 10 and add charin's digit value.} number := number*10 + DigitVal(charin)
ELSE IF charIn='C THEN InitNums {Clear sum on 'C'.}
ELSE IF charln='+' THEN {Add on '+'}
BEGIN sum := sum + number; PrintNums; number := 0 END
ELSE InitNums; (Clear sum and number on bad input characters.} read(charIn); {Always get next character.}
END;
WritelnfAll done — bye'); (Got an ’S' — stop.} END.;
Рис. 2.3. Программа моделирования простого сумматора, составленная на языке Паскаль.
тора и циклического выполнения последовательностей простых операторов.
Процедурный аппарат широко используется для разработки программ, которые представляют собой совокупность модулей с хорошо определенным интерфейсом. При создании, программ
в виде иерархически организованной совокупности процедур используется так называемый метод проектирования по принципу сверху вниз, согласно которому программа реализуется в виде нескольких модулей высокого уровня (процедур и функций), каждый из которых определяется модулями более низкого уровня.
Процедура представляет собой последовательность описаний операторов. Она может быть вызвана с помощью одного оператора. Функция определяется так же, как и процедура, однако для обращения к ней необходимо написать ее имя в выражении, что аналогично обычному использованию имени переменной. Помимо применения стандартных процедур и функций в каждой программе на Паскале допускается определение собственных процедур и функций. Как показано на рис. 2.2, проектирование по принципу сверху вниз в языке Паскаль обеспечивается использованием идентичной структуры для процедур и функций как в главной программе, так и в каждой процедуре и функции, в которых могут быть определены собственные процедуры и функции.
Областью существования элемента, определенного в программе или процедуре, является часть программы, в которой этот элемент может быть использован. Область существования элемента, определенного в данном блоке, ограничивается этим блоком и всеми блоками меньшего размера, содержащимися в нем (см. рис. 2.2). Элементы, определенные во внешнем блоке, охватывающем другие блоки, называются глобальными; элементы, определенные в некотором внутреннем блоке, являются локальными для этого блока. Таким образом, программист может определять локальные переменные, структуры данных и процедуры внутри одного блока, не принимая во внимание определения элементов в других блоках того же самого или более высокого уровня.
Пример программы, приведенной на рис. 2.3, дает представление о том, как выглядит реальная программа на Паскале. Подобно всем хорошим программам, данная Паскаль-программа является замкнутой, что позволит сведущему в языке разобраться в ней без какой-либо другой документации. В оставшейся части настоящей главы будут рассматриваться основные понятия языка Паскаль.
ЛЕКСИКА
На наиболее низком уровне программа на Паскале представляет собой последовательность букв, цифр и специальных символов, распознаваемых компилятором. Специальный символ может быть одной литерой или парой литер. Набор специальных
Таблица 2.1. Специальные символы языка Паскаль
Специальные символы
;:.,'<>{} ( ) { ]
А = + -*/:=<=>= <> ..
символов языка Паскаль представлен в табл. 2.1. Среди специальных символов есть два символа, не имеющих графического изображения. Это «пробел» и «возврат каретки». Кроме того, в языке определяются так называемые зарезервированные "(ключевые) слова (табл. 2.2). Все другие компоненты программы представляют собой последовательности литер и образуют числа, идентификаторы и строки.
Таблица 2.2. Зарезервированные слова языка Паскаль
Зарезервированные слаба
AND
END
NIL
SET
ARRAY
FILE
NOT
THEN
BEGIN
FOR
OF
TO
CASE
FUNCTION
OR
TYPE
CONST
GOTO
PACKED
UNTIL
DIV
IF
PROCEDURE
VAR
DO
IN
PROGRAM
WHILE
DOWNTO
LABEL
RECORD
WITH
ELSE
MOD
REPEAT
Числа записываются в десятичной системе счисления; при этом используется либо представление чисел с фиксированной точкой (17, —999, 123.456, 0.000000005168), либо представление чисел с плавающей точкой (17Е0, —99.9Е1, 123456Е—3, 5.168Е—9), в котором Е означает «умножить на 10 в степени». Идентификатор — это последовательность букв и цифр, начинающаяся с буквы. Программа может содержать произвольно выбираемые идентификаторы, длина которых ограничена только допустимой длиной вводимой в ЭВМ строки. Однако распознаются идентификаторы по первым восьми знакам, т. е. комбинация первых восьми букв и цифр каждого идентификатора должна быть единственной. Кроме того, в любой версии Паскаля определяется некоторое количество стандартных идентификаторов; программист может переопределить эти идентификаторы, но делать это нецелесообразно. Стандартные иденти-
Таблица 2.3. Стандартные идентификаторы языка Паскаль
Файлы
Константы
Типы
Процедуры
функции
input
false
boolean
get readln
abs odd arctan
output
true
integer
new reset
chr ord succ
maxint
real
pack rewrite
cos pred trunc
char
page unpack
eof sin eoln
text
put write
exp sqr round
read writein
In sqrt
фикаторы, включаемые во все версии Паскаля, представлены в табл. 2.3; конкретные версии могут иметь дополнительные стандартные идентификаторы.
Опытные программисты так выбирают идентификаторы, чтобы был понятен их смысл или назначение. Программисты применяют различные приемы для отделения слов в идентификаторе, состоящем из нескольких слов. Например, в некоторых компиляторах языка Паскаль знак «черта снизу» позволяет программисту записывать идентификаторы таким образом: item.count, stack-size и get.next.char. В некоторых версиях Паскаля используются как прописные, так и строчные буквы, поэтому лица, работающие с этим языком, вводят определенные соглашения относительно их использования. Мы будем придерживаться следующих соглашений:
Зарезервированные слова, которые в реальных программах печатают строчными буквами, в примерах, приводимых по ходу изложения материала, записаны прописными буквами.
«Тело» идентификатора представляется строчными буквами.
Определяемые пользователем имена программ, процедур и функций начинаются с прописной буквы; все другие идентификаторы начинаются со строчной буквы.
Второе и последующие слова в идентификаторе, составленном из нескольких слов, начинаются с большой буквы.
Таким образом, head, itemCount и stackSize могут быть именами переменных, в то время как идентификаторы Sort, Merge, GetNextChar и TicTacToe являются именами программы, процедуры или функции, которые определяются самим программистом. В стандартном Паскале подходящие строчные и прописные буквы могут на равных правах использоваться при написании идентификаторов и зарезервированных слов. Несоблюдение указанных выше правил использования прописных букв в идентификаторах никак не повлияет на работу компилятора, но после редактирования и распечатки исходного текста программы все допущенные отклонения станут очевидными.
Последовательность литер, заключенная в одиночные кавычки (апострофы), называется строкой, например 'строка' в соответствии с этим определением является строкой. В строках могут использоваться прописные буквы. Для включения в строку кавычки необходимо записать ее дважды, например: 'Don"t think twice, it"s alright!'
Последовательность литер, заключенная в фигурные скобки и не содержащая в себе фигурных скобок, называется комментарием. Комментарии игнорируются компилятором. Пробелы, комментарии и символ «возврат каретки» используются в качестве разделителей. Между последовательно записанными зарезервированными словами и идентификаторами должен быть по крайней мере один разделитель. Не требуется, но вполне допустимо использовать разделители между специальными символами и некоторыми другими знаками. Программу можно написать, употребляя минимальное число разделителей, однако следует помнить, что использование разделителей улучшает зрительное восприятие текста программы. Примеры программы, приведенные в этой и последующих главах, иллюстрируют правила рационального использования разделителей при написании текста программы.
СТРУКТУРА ПРОГРАММЫ
На высоком уровне программа на языке Паскаль состоит из описаний и операторов. Описания располагаются в начале программы. Они определяют имя программы, имена и типы констант и переменных, используемых в программе, и применяемые процедуры и функции. Операторы указывают действия, которые будут выполняться по программе.
Формально программа состоит из заголовка программы и блока, после которого ставится точка. Такую структуру программы можно представить синтаксической диаграммой (рис. 2.4, а). На синтаксической диаграмме в прямоугольники заключены имена элементов программы, которые где-то должны быть определены. В рассматриваемом примере такими именами являются заголовок программы и блок. В кружках или в блоках с закругленными углами записываются специальные символы или зарезервированные слова. Синтаксическая диаграмма задает только синтаксис элементов программы, т. е. формат, определяющий структуру элементов. Значение, или семантика, элементов программы. должно описываться отдельно.
Синтаксические диаграммы элементов программы представлены на рис. 2.4, бив. Заголовок программы состоит из зарезервированного слова PROGRAM, после которого записывают-
ся идентификатор и заключенный в круглые скобки список из одного или нескольких идентификаторов. В конце заголовка программы ставится точка с запятой. Блок состоит из шести ча-
ПрограммаПрограмма* Блок* БлокЗаголовок • программы •Заголовок • программы •ББРис. 2.4. Синтаксические диаграммы программы (а), заголовка программы (б) и блока (в).Рис. 2.4. Синтаксические диаграммы программы (а), заголовка программы (б) и блока (в).
Программа
Программа
* Блок
* Блок
Заголовок • программы •
Заголовок • программы •
Б
Б
Рис. 2.4. Синтаксические диаграммы программы (а), заголовка программы (б) и блока (в).
Рис. 2.4. Синтаксические диаграммы программы (а), заголовка программы (б) и блока (в).
стей, размещаемых в заданном порядке (см. рис. 2.4, в); любая часть, кроме операторной, является необязательной.
Заголовок программы на Паскале записывается всегда как первая строка программы. В нем определяются имя программы
и файлы, доступные программе, например:
PROGRAM Example (input,output);
Программа с именем Example читает данные из стандарт» ного файла input и направляет получаемые результаты в стандартный файл output. Соответствие между входным и выходным файлами и устройствами ввода-вывода определяется конкретной версией Паскаля. Например, в системах с пакетной обработкой заданий для ввода используется устройство считыва-
Составной операторСоставной операторСоставной операторСоставной операторРис. 2.5. Синтаксические диаграммы раздела операторов и составного оператора.Рис. 2.5. Синтаксические диаграммы раздела операторов и составного оператора.Раздел операторов
Составной оператор
Составной оператор
Составной оператор
Составной оператор
Рис. 2.5. Синтаксические диаграммы раздела операторов и составного оператора.
Рис. 2.5. Синтаксические диаграммы раздела операторов и составного оператора.
ния с перфокарт, а для вывода — построчно-печатающее устройство; в системах с разделением времени как для ввода, так и для вывода применяются поименованные файлы, зарегистрированные в справочнике пользователя; в настольной микро-ЭВМ ввод осуществляется с помощью клавишного пульта, а для вывода служит экран дисплея. В программе могут использоваться и другие файлы, которые должны определяться в заголовке программы, однако вопросы обработки файлов в Паскале выходят за рамки нашего обсуждения.
PROGRAM Example (input,output);
VAR x, square, recipr : real; 4
BEGIN
read (x); .
square := x*x;
recipr := 1/x; .
write (‘Square of1,, x, 'is', square, 'reciprocal is', reciprj END.
Рис. 2.6. Пример простой программы на языке Паскаль.
Диаграмма, изображенная на рис. 2.4, в, определяет синтаксическую структуру блока; пять разделов описаний являются необязательными, но раздел операторов всегда входит в блок. Синтаксические правила для каждого вида описаний изложены в следующем разделе; раздел операторов определяется синтаксической диаграммой, показанной на рис. 2.5. Раздел операто-
ров представляет собой составной оператор, содержащий последовательность операторов, заключенную между зарезервированными словами BEGIN и END. Синтаксические диаграммы отдельных операторов рассматриваются в разд. 2.7—2.9.
Пример простой программы на Паскале представлен на рис. 2.6. В этой программе отсутствуют описания меток, констант, процедур и функций.
ОПИСАНИЯ
Описания, как определено в предыдущем разделе, помещаются в начале блока. Каждая программа на Паскале содержит по крайней мере один блок; процедура или функция также содержит блок, а следовательно, и объявления. Синтаксические диаграммы объявлений представлены на рис. 2.7.
Рис. 2.7. Синтаксические диаграммы объявлений.
В разделе описания меток задается список меток, используемых в блоке, например:
LABEL 17, 6851,357;
В качестве меток в языке Паскаль используются целые числа без знака. Для записи метки можно использовать не более четырех цифр.ГМетками снабжаются операторы, которым передается управление при выполнении оператора перехода GOTO. Все метки должны быть объявлены.ГЕсли в программе нет помеченных операторов, то раздел описания меток в соответствующем блоке должен отсутствовать.
Раздел определения констант содержит список равенств* в левой части которых записываются идентификаторы, а в пра-
4—1393
вой — константы. После каждого равенства ставится точка с запятой. Так, можно сделать следующее описание констант:
CONST pi=3.14159265;
first=l; last=50;
heading—'значения площади окружности';
Тип каждого идентификатора определяется типом соответствующей константы. Если не предполагается описывать в блоке константы, то соответствующий раздел описаний должен быть опущен.
В разделе .определения констант каждой константе ставится в соответствие идентификатор, который играет роль ее синонима. Такой способ определения констант хорошо известен в языке ассемблера, однако в большинстве языков высокого уровня его не используют. Каждый идентификатор компилятор заменяет на соответствующую константу. Использование удачно подобранных идентификаторов констант вместо чисел улучшает восприятие программы при ее чтении. Более существенно, что применение идентификаторов в качестве синонимов констант облегчает проведение модернизации программ и упрощает их сопровождение, поскольку программист может изменить некоторые важные параметры, например диапазон входных данных или размер внутренней структуры данных, затрагивая при этом только одну строку объявлений. Отсутствие такой возможности потребовало бы проведения поиска констант по всей программе и изменения некоторой константы столько раз, сколько она встречается в тексте программы.
Раздел определения типов содержит список равенств, в левой части которых записываются идентификаторы, а в правой — так называемые типы. В разд. 2.6 будут показаны два способа задания в программах на Паскале типов, определяемых самим пользователем. Если в программе не определяются новые типы, то раздел определения типов опускается.
Каждая переменная, используемая в программе на языке Паскаль, должна описываться в разделе описания переменных, который содержит списки идентификаторов переменных и определенный тип, соответствующий переменным каждого списка. Слева записывают один или несколько идентификаторов, затем ставят двоеточие, за которым следует тип, например
VAR
count, id, nScores: integer;
average, sigma: real;
inputChar: char;
greater, finished: boolean;,.
stripe 1, stripe2, stripe3: colorCode;,■
Тип данных может быть либо стандартным, либо типом, задаваемым пользователем в разделе определения типов. Каждая реальная программа имеет по крайней мере одну переменную, поэтому раздел описания переменных всегда присутствует в блоке программы. Однако можно себе представить процедуру или функцию, в которых нет локальных переменных, в таком случае раздел описания переменных отсутствует.
Синтаксическая диаграмма раздела описания процедур и функций представлена на рис. 2.8. Описание процедуры ил»
Раздел описания процедур^ и функцийРаздел описания процедур^ и функцийЗаголобок процедурыЗаголобок процедурыЗаголовок функцииЗаголовок функцииРис. 2.8. Синтаксическая диаграмма раздела описания процедур и функций-Рис. 2.8. Синтаксическая диаграмма раздела описания процедур и функций-
Раздел описания процедур^ и функций
Раздел описания процедур^ и функций
Заголобок процедуры
Заголобок процедуры
Заголовок функции
Заголовок функции
Рис. 2.8. Синтаксическая диаграмма раздела описания процедур и функций-
Рис. 2.8. Синтаксическая диаграмма раздела описания процедур и функций-
функции по своей структуре подобно описанию программы. Определение блока, входящего в процедуру или функцию, было- дано в предыдущем разделе. Заголовок процедуры или заголовок функции задает имя процедуры или функции и список их параметров. (Более подробно вопросы описания процедур и функций и их использования изложены в гл. 9.) Процедура или функция может не иметь параметров, список параметров в таком случае будет пуст. Простые примеры описания процедур в функций приведены на рис. 2.3.
Легко заметить, что описание процедуры или функции имеет рекурсивную природу: действительно, блок, содержащийся в этом описании, в свою очередь может содержать описания процедур или функций. Это позволяет вести проектирование по- принципу сверху вниз, так как любая процедура может содержать в себе определения собственных процедур. Вместо описания программы высокого уровня и задания множества процедур* низкого уровня последние могут определяться и использоваться внутри процедур высокого уровня.
СТАНДАРТНЫЕ ТИПЫ ДАННЫХ И ВЫРАЖЕНИЯ
В Паскале типы данных подразделяются на простые и -структурные. Простой тип данных определяет упорядоченное множество значений, например целые числа или множество литер. Стандартные простые типы — целочисленный (integer), ве- гцественный (real), литерный (char) и булев (boolean)—описываются в следующих четырех подразделах. Структурный тип характеризуется типами его компонентов и методом структурирования. Простейшей структурой является массив (см. разд. 3.1 и 3.2). '
Данные используются для образования различных выражений. Так, сравнивая два аргумента одного типа с помощью знака отношения или сравнения:
— равно < меньше > больше— равно < меньше > больше< = меньше или равно > = больше или равно О не равно
— равно < меньше > больше
— равно < меньше > больше
получим выражение, называемое «отношением».
В арифметическое выражение входят аргументы вещественного, целочисленного или ограниченного целочисленного типа. Вычисление выражения, согласно правилам, изложенным в следующих двух подразделах, приводит к результату определенного типа. Так, булевы выражения, рассматриваемые ниже, содержат аргументы булева типа; результатом вычисления таких выражений является величина булева типа.
Целочисленный тип данных
Значение целочисленной переменной или константы представляет собой целое число в диапазоне [—maxint, +maxint], где maxint является величиной, зависимой от разрядности ЭВМ. В большинстве современных ЭВМ для представления целых чисел используется дополнительный двоичный код, поэтому та- xint=2w~1, где w есть длина машинного слова. Например, для типичных 16-разрядных микропроцессоров maxint=32 767. Константа целочисленного типа представляет собой последовательность десятичных цифр, перед которой может быть поставлен знак «плюс» или «минус».
В языке Паскаль результатом выполнения арифметических операций сложения (+), вычитания (—) и умножения (*) над операндами целочисленного типа является величина целочисленного типа. Кроме того, определены две операции для выполнения деления, которым свойствен целочисленный результат: целочисленное деление DIV обеспечивает получение целой части частного (например, 9DIV4=2); операция MOD определи-
ет остаток от целочисленного деления (например, 9 MOD 4=1). Обычная операция деления (/), примененная к операндам целочисленного типа, дает результат вещественного типа (например, 9/4=2.25).
"2.5.2. Вещественный тип данных
Вещественные величины являются элементами зависимого -от способа реализации в ЭВМ подмножества вещественных чисел, представляемых в машине в виде чисел с плавающей точкой. Представление чисел с плавающей точкой позволяет использовать как положительные, так и отрицательные числа, абсолютные значения которых обычно заключены в диапазоне 10-38—юзе (см разд. 4.11). Вещественная константа — это либо последовательность десятичных цифр со знаком, которая может включать десятичную точку, либо десятичное число, записанное в виде числа с плавающей точкой. (Величина вещественной константы должна начинаться с цифры; таким образом, следует писать 0.005 или 5Е—3, но не .005 или .5Е—2.
Для выполнения действий над вещественными операндами можно использовать операции сложения, вычитания, умножения и деления. Результатом деления всегда является величина вещественного типа, другие операции приводят к вещественному результату тогда, когда хотя бы один операнд имеет вещественный тип.
Целочисленный результат вычисления некоторого выражения может быть присвоен переменной вещественного типа; при этом произойдет автоматическое преобразование целочисленного значения в вещественное значение. Однако нельзя присваивать вещественное значение переменной целочисленного типа. Для преобразования вещественных значений в целочисленные используются функции trunc и round: первая функция обеспечивает отбрасывание дробной части вещественной величины, вторая формирует целое число, ближайшее к данной вещественной величине^
Как и в других языках высокого уровня, операции умножения и деления в выражениях выполняются до операций сложения и вычитания; при этом для записи выражений допускается использование скобок. Тогда правые и левые части приведенных ниже равенств эквивалентны: a*b+c*d= (a*b) + (c*d) a+b/c+d— а+ (b/c) +d a*b/c= (a*b)/c
Хотя приоритеты арифметических операций точно определены, скобки можно использовать для определения, порядка
действий в тех случаях, когда последовательность выполнения операций без использования скобок не представляется очевидной. Следующий пример показывает, что путем расстановки скобок можно иногда повлиять на результат выполнения целочисленных операций:
5.0*3.0/2.0= (5.0*3.0)/2.0=5.0*(3.0/2.0) =7.5 5*3 DIV 2= (5*3) DIV 2-7< >5*(3 DIV 2) -5
Литерный тип данных
Величина литерного типа является элементом множества литер, используемых для взаимодействия с ЭВМ. Как множество литер, распознаваемых и печатаемых машиной, так и внутреннее представление литер строками битов зависит от используемых аппаратных средств. Однако существуют стандартный набор символов и соответствующие им комбинации битов, называемые Стандартным американским кодом для обмена информацией (ASCII — American Standard Code for Information Interchange) (см. приложение А). Этим кодом пользуются почти все фирмы — изготовители вычислительных машин. ■
Литера, заключенная в одиночные кавычки, представляет собой константу литерного типа. Множество литер упорядочено. В большинстве наборов кодов упорядоченность литер определяется упорядоченностью числовых значений соответствующих последовательностей битов. Так, например, в коде ASCII 'В'>'А', 'Q'C'q' и '+'<?'. .
Булев тип данных
Имеются два булевых значения — true (истинно) и false (ложно). В Паскале определена логическая операция NOT (НЕ), которая для булева операнда х дает противоположное булево значение (см. табл. 2.4). В операции отрицания используется только один операнд, поэтому она называется унарной. В бинарных операциях участвуют два операнда. Правила выполнения бинарных логических операций AND (И) и OR (ИЛИ), используемых в языке Паскаль, определены в табл. 2.4. Используя булевы операнды и рассмотренные логические операции, можно записывать в программах сложные булевы выражения.
Когда с помощью операций сравнения сопоставляют операнды каких-либо типов, результатом сравнения является величина булева типа. Например, если переменная j имеет целочисленный тип, то выражение j>l имеет значение «истинно» только тогда, когда значение переменной j больше 1, в противном случае это выражение принимает булево значение «лож-
Таблица 2.4. Булевы операции в языке Паскаль
X
У
НЕ х
х ИЛИ у
х И у
ложно
ложно
истинно
ложно
ложно
ложно
истинно
истинно
истинно
ложно
истинно
ложно
ложно
истинно
ложно
истинно
истинно
ложно
истинно
истинно
но»; отношение 1 = 1 всегда есть «истинно», а выражение 5 <2 — «ложно».
Ниже даны другие примеры булевых выражений. В этих примерах используются переменные ent и limit целочисленного типа, переменная inChar литерного типа и переменная testOK булева типа.
ent <=50
(ent<limit) AND (inCharOV)
((ent>limit—10) AND (inChar='?')) OR NOT testOK
Как и в арифметических выражениях, порядок выполнения операций в булевых выражениях языка Паскаль строго определен. Однако, когда порядок действий не представляется очевидным, целесообразно использовать круглые скобки; это придает ясность записи выражений и уменьшает возможность неверной записи.
ТИПЫ ДАННЫХ, ОПРЕДЕЛЯЕМЫЕ
ПОЛЬЗОВАТЕЛЕМ
В языке Паскаль предусмотрено несколько классов типов данных, определяемых пользователем. В этом разделе описываются простейшие из них — перечислимый и ограниченный типы. Использование типов данных, определяемых пользователем, в ряде случаев позволяет разработать более качественную программу. Применение перечислимых типов данных позволяет проще запрограммировать нечисловую задачу по сравнению с вариантом, когда в программе записывается длинный ряд объявлений констант в виде равенств, содержащих в левой части идентификаторы, а в правой — числа, например: «черный = 0; коричневый=1; красный=2; ...». Ограниченные типы данных определяют ограниченный диапазон значений переменных, что позволяет компилятору производить более полный контроль ошибок и создавать более эффективные программы.
Тип данных множество (set), который мы рассматривать не будем, позволяет естественным образом формулировать многие
комбинаторные задачи. Структурные типы данных (некоторые из них обсуждаются в гл. 3) позволяют явно задавать сложные структуры данных; такие определения данных понятны пользователю и легко воспринимаются компилятором.
Перечислимые типы данных
В разд. 1.6 отмечалось, что последовательность битов, не нуждается в представлении числом, поскольку она сама является двоичным числом. Распознавая эти объекты, ЭВМ обрабатывают различные нечисловые данные. В языке Паскаль реализован формальный механизм для определения нечисловых перечислимых типов данных, на значения которых в программе можно ссылаться посредством нечисленных обозначений, т. е. имен. Новые перечислимые типы вводятся в программе в разделе определения типов; в приведенном ниже примере определяются три перечислимых типа данных:
TYPE
color—(черный, коричневый, красный, оранжевый, желтый, зеленый, голубой, фиолетовый, серый, белый, серебряный, золотой, бронзовый, индиго, розовый, терракотовый);
city— (сентЛуис, майами, Чикаго, санФранциско, Детройт, ньюИорк, бостон, Хьюстон, Денвер, Мемфис, портленд, милуоки, Сиэтл, джуно, Гонолулу» альбукерке);
direction— (север, юг, восток, запад);
После того как определен новый тип данных, могут был» объявлены переменные этого типа:
VAR
birthplace, origin, destination: city;
rout, alternate: direction;
favorite: color;
Переменные перечислимых типов могут описываться и без указания имени типа, например:
VAR rout, alternate: (север, юг, восток, запад);
Идентификаторы, содержащиеся в списке определения типа, становятся константами этого типа. Переменной может присваиваться значение переменной или константы того же типа; однако нельзя в операторах присваивания использовать выражения» В приведенном ниже примере первые четыре оператора присваивания являются правильными, а последние три — неверными.
(правильные операторы} origin := Чикаго; favorite := терракотовый; alternate := rout; rout := север;
(неправильные операторы} destination := запад; favorite := birthplace; birthplace : = origin OR destination;
Константы, входящие в определение перечислимого типа, считаются упорядоченными, т. е. им ставится в соответствие последовательность целых чисел, начинающаяся с 0. Эти порядковые номера компилятор использует для представления констант в памяти ЭВМ. Значением стандартной функции является порядковый номер значения переменной или константы перечислимого типа, например: ord (белый) «9 ord (санФранциско) =3 ord (север) =0
Значениями стандартных функций pred и succ в соответствии с задаваемым упорядочением констант являются предыдущий и последующий по отношению к аргументу элементы списка определения. Приведем примеры использования этих функций: pred (зеленый) = желтый ~succ (восток) = запад pred (милуоки) = портленд • succ (запад) = {не определено} pred (черный) = {не определено}
Над данными перечислимых типов могут выполняться только операции сравнения. Когда производят эту операцию над двумя аргументами, результат получают путем сравнения соответствующих порядковых номеров, например:
(чикаго > санФранциско) = (2 > 3) = ложно • ,
Могут рассматриваться как перечислимые целочисленные, литерные и булевы типы данных. Для них справедливы предыдущие рассуждения и поэтому допустимы такие операции, как
pred (29) =28 succ(—5)=—4 ord (false) = 0 ord (true) = 1 ialsectruepred (29) =28 succ(—5)=—4 ord (false) = 0 ord (true) = 1 ialsectruesucc('i')='j' pred ('8') ='7' ord('9')<>9 ord('9')—ord('0')=9
pred (29) =28 succ(—5)=—4 ord (false) = 0 ord (true) = 1 ialsectrue
pred (29) =28 succ(—5)=—4 ord (false) = 0 ord (true) = 1 ialsectrue
Правильность выполнения операций над данными литерного типа гарантируется только тогда, когда литеры имеют такие же порядковые значения, как и символы кода ASCII (см. приложение А). Так, например, при использовании кода EBCDIC, применяемого в машинах фирмы IBM, отношение succ ('!') =/j/ будет иметь значение «ложно». Следовательно, желательно избегать употребления выражений, в которых используются литеры с идущими подряд порядковыми номерами. Однако в соответствии со стандартом языка Паскаль, разработанным Институтом инженеров по электротехнике и радиоэлектронике (IEEE), множество литер должно обладать следующими свойствами:
порядковые номера цифр 'О'—'У должны быть упорядочены и следовать друг за другом;
порядковые номера прописных букв 'Д'—'Z' должны быть упорядочены по возрастанию, однако следование номеров подряд не является обязательным.
порядковые номера строчных букв 'а'—'z' должны быть упорядочены по возрастанию, однако следование номеров подряд не является обязательным.
Ограниченные типы данных
К ограниченному типу данных относятся элементы подмножества подряд идущих величин некоторого базового типа, который может быть перечислимым или любым простым типом, _за исключением вещественного. Ограниченный тип определяется в разделе определения типов посредством указания первого и последнего значений подмножества подряд расположенных величин базового типа. Например, можно определить следующие ограниченные типы данных:
TYPE
digit=0..9;
digitChar='0'./9/;
colorCode=черный.. белый;
т. е. типы digit, digitChar и colorCode, которые являются ограниченными типами базовых типов integer, char и color соответственно. Переменные ограниченных типов могут определяться подобно тому, как описываются переменные перечислимых типов:
VAR
units, tens, hundreds: digit;
outChar: digitChar;
bandl, band2, band3 : colorCode;
index: 1..100;
В выражениях и в левой части оператора присваивания можно использовать переменные ограниченных типов аналогично тому, как в них могут употребляться переменные соответствующих перечислимых типов. Однако попытка во время выполнения программы присвоить переменной ограниченного типа значение, выходящее за определенные границы, приведет к ошибке.
Переменные целочисленного типа, особенно при использовании их в качестве счетчиков, для индексирования или для нумерации, редко принимают в программе непредсказуемые значения. Используя ограниченные типы данных, программист фактически информирует компилятор о допустимом диапазоне значений объявляемых им переменных. Вследствие этого отклонения от нормального хода выполнения программы, причиной которых могут быть некорректные входные данные или ошибки программирования, можно обнаруживать путем контроля принадлежности значений переменных определенному диапазону. Программные средства для выполнения таких проверок компилятор автоматически включает в программу. Кроме того, ограниченные типы данных имеют большое значение для синтаксиса языка Паскаль: они применяются в операторах CASE и в объявлениях массивов (см. разд. 2.9 и 3.1 соответственно).
ПРОСТЫЕ ОПЕРАТОРЫ
Операторами являются последовательности зарезервированных слов, идентификаторов и специальных символов; эти последовательности описывают действия в программе на Паскале. Операторы в каждом блоке программы на Паскале выполняются в том порядке, в котором они были записаны, т. е. последовательно. В языке Паскаль определены операторы двух типов: простые и структурные. Простым называется оператор, который не содержит в себе других операторов. В этом разделе описываются три типа простых операторов. Пустой оператор, который никак не обозначается и не вызывает никаких действий в программе, также относится к простым операторам. После ознакомления с разделом о структурных операторах читателю станет понятно, что иногда в таких операторах удобно использовать пустой оператор.
Операторы присваивания
Оператор присваивания выполняет присваивание значения выражения, записанного справа от специального символа :=, переменной, находящейся слева от этого символа. Типы переменной и выражения должны быть одинаковыми. Из этого пра-
вила имеются лишь два исключения: а) если выражение имеет значение целочисленного или ограниченного целочисленного' типа, то переменная может быть вещественного типа; б) тип' выражения может быть ограниченным типом, переменной, и наоборот. Несколько примеров операторов присваивания было- дано выше; на рис. 2.9 приведен еще ряд примеров.
VAR
j, k : integer;
a, b, с : real;
outChar, inChar : char;
done, valid : boolean;
BEGIN
i := j + к - 1;
a := j + к - 1; *
к := j*i + 5;
b := a*b/c;
c := b*a/1E10;
inChar := ’q1; outChar := inChar;, valid := (i<j) AND ((inCharOoutChar). OR (03.14));- done := NOT valid;
END.
Рис. 2.9. Примеры операторов присваивания.
Оператор перехода СОТО
Любому оператору языка Паскаль может предшествовать конструкция вида «метка-.», в которой метка — это целое число без знака, состоящее не более чем из четырех цифр. Числа, являющиеся метками, должны быть указаны в разделе описания меток; каждая метка в блоке должна быть единственной. Оператор перехода имеет вид «GOTO метка-», его выполнение заключается в передаче управления соответствующему помеченному оператору. Это означает, что следующим оператором после оператора перехода будет выполняться помеченный оператор и реализация программы будет продолжена из этой новой точки.
Применение оператора GOTO обычно усложняет программу. Дело в том, что передача управления «вперед», и особенно «назад», затрудняет чтение программы, "ее отладку и проверку правильности. Структурные операторы, рассматриваемые в следующем разделе, предоставляют в распоряжение программиста более четкие по построению и более наглядные для восприятия средства управления программой. Мы продемонстрируем это в дальнейшем с помощью примеров, показывающих, какие операторы необходимо использовать вместо структурных операто-
ров и как при этом будут выглядеть некоторые фрагменты программ.
Наиболее целесообразно использовать оператор GOTO для выхода из структурного оператора при возникновении особой ситуации в программе. Переход к структурному оператору извне приводит к непредсказуемому результату. '
Оператор процедуры
Процедура представляет собой определенную последовательность описаний и операторов (т. е. некоторый блок), которая может быть вызвана одним оператором, называемым оператором процедуры. Более подробно процедуры будут рассматриваться в гл. 9, а здесь мы изложим лишь общие понятия.
В языке Паскаль имеются четыре встроенные процедуры, которые используются в программах для ввода и вывода данных. Они могут быть вызваны с помощью следующих операторов процедуры:
read {переменная!,переменная2,...,переменнаяп) — обеспечивает чтение данных из стандартного файла с именем input и присвоение прочитанных значений соответствующим переменным списка. Типы вводимых значений должны совпадать с типами соответствующих переменных.
readin {переменная!,переменная2,...,переменнаяп) — выполняется так же, как и процедура read, за исключением того, что по окончании чтения последней переменной осуществляется переход к началу новой строки стандартного файла input.
write {выражение 1,выражение2,...,выражениеп) — выполняет вывод в стандартный файл output значений, указанных в списке выражений.
writein {выражение!,выражение2,...,выражениеп) — выполняет такие же действия, как и оператор write, за исключением того, что после вывода последнего выражения производится переход к новой строке стандартного файла output.
Кроме того, с помощью этих процедур можно выполнять чтение из файлов и запись в файлы, отличные от стандартных файлов input и output; при этом можно использовать некоторые простые средства задания форматов данных. В языке Паскаль предусмотрено несколько других встроенных процедур, имеющих отношение к вводу-выводу; их действие в некоторой степени определяется используемой версией языка.
Как и в других языках высокого уровня, в Паскале предусмотрена возможность использования процедур, определяемых пользователем. Рассмотрим задачу вычисления и печати значения обратной величины, квадрата и целой части вещественной переменной а:
VAR a, recipr, square : real; int: integer;
recipr:— 1/a;
square := a*a;
int := trunc(a);
writeln (a,recipr,square,int);
Если мы хотим выполнить такие же вычисления для вещественных переменных Ь, с и d, то необходимо написать три аналогичные последовательности, в каждой из которых переменная а
PROGRAM Print4Values (input,output);
{Declare variables used by the program}
VAR a, b, c, d : real;
{Declare the printing procedure} PROCEDURE PrintVal (x : real); {declare local variables}"
VAR recipr, square : real; int : integer;
> BEGIN {This is the statement part of the procedure) recipr := 1/x; square := x*x; int := trunc(x);
writeln (x,recipr,square,int) ;
END;
{Now comes the statement part of the main program}
BEGIN'
read (a,b,c,d) ;
PrintVal(a) ; Pr int\ al (b) ; PrintVal(c); PrintVal (d); END.
Рис. 2.10. Пример программы с одной процедурой.
будет заменена на Ь, с и d соответственно; переменные recipr, square и int будут при этом использоваться для временного хранения результатов вычислений. В программе, представленной на рис. 2.10, описана процедура, предназначенная для вычисления интересующих нас значений и вывода их на печать; эта процедура вызывается в программе для определения требуемых функций при произвольных вещественных значениях аргумента. Если исходными данными для этой программы являются числа 4.5, 1.7, 0.5 и 6.8, то на печать выводятся следую-
щие результаты:
4.5000
0.2222
20.2500
4
1.7000
0.5882
2.8900
1
0.5000
2.0000
0.2500
0
6.8000^0.1471
46.2400
6
В процедуре, описанной в рассматриваемой программе, переменная х называется формальным параметром; переменные а, Ь, с и d в операторах процедуры PrintVal называются фактическими параметрами. При обращении к процедуре, прежде чем она будет выполнена, в ее теле производится подстановка фактических параметров вместо формальных. В языке Паскаль имеется несколько способов осуществления таких подстановок (см. разд. 9.2).
Благодаря тому что при использовании указанной процедуры требуется только один список неоднократно повторяемой последовательности операторов, экономится время разработки программы и уменьшается объем памяти, необходимой для размещения программы во время ее выполнения. Процедуры и функции являются неотъемлемыми элементами не только языка Паскаль, но и любого другого языка, будь то язык высокого уровня или ассемблер. Применение процедурного аппарата способствует построению модульных программ, которые легче читать и отлаживать; это связано с тем, что, используя принцип модульности, можно некоторую задачу представить в виде структурированной группы небольших задач с четко определенными связями.
СТРУКТУРНЫЕ ОПЕРАТОРЫ
(ГРУППА I)
Структурный оператор в качестве своих компонентов содержит один или несколько операторов. В языке Паскаль определено несколько структурных операторов, которые позволяют программировать ветвления и циклы, а в-совокупности являются средством реализации в программе основных концепций управления. Мы будем рассматривать все структурные операторы, языка Паскаль, за исключением оператора присоединения (оператора WITH), который используется при работе с переменными типа «запись» (RECORD).
Операторы условного перехода, выбора и цикла языка Паскаль можно сопоставить с элементарными средствами, предусмотренными в машинных языках типичных ЭВМ. При выполнении машинной команды условного перехода сначала проверяется некоторое условие и в зависимости от результата этой проверки либо производится передача управления другой последовательности команд, либо продолжается выполнение текущей последовательности команд. Такая машинная команда выполняется подобно тому, как реализуется следующий оператор языка Паскаль:
IF условие THEN GOTO метка
Рассмотрение операторов условного перехода, выбора и цикла языка Паскаль будет сопровождаться их сравнением с последовательностями операторов, в которых для организации управления используется представленный выше упрощенный ■структурный оператор. Кроме того, при описании в третьей части книги архитектуры конкретных моделей микро-ЭВМ будут приведены фрагменты программ на языке ассемблера, эквивалентные отдельным операторам языка Паскаль.
Составной оператор
Составной оператор — это заключенная между зарезервированными . словами BEGIN и END последовательность операторов, отделенных друг от друга точкой с запятой. Составной оператор имеет следующую общую форму:
BEGIN'
оператор!;'
оаератор2;
оператора—1;-
оператора; END
Синтаксическая диаграмма составного оператора была приведена на рис. 2.5. Мы уже видели много примеров применения составного оператора. Так, по существу, разделы операторов программы, процедуры или функции представляют собой составной оператор.
Каждый оператор, входящий в составной, может быть простым или структурным; структурные операторы в свою очередь могут содержать составные операторы. Такое «рекурсивное» определение свойственно структуре языка Паскаль. Эффективность всех других структурных операторов (кроме составного) обусловлена рекурсивной природой их определения: операторы IF, FOR, WHILE, REPEAT и CASE с той же легкостью управляют сложным структурным оператором, как и простым оператором.
Относительно определения составного оператора следует добавить, что точка с запятой в нем используется как разделитель, а не признак окончания оператора. (В некоторых блочно-структурированных языках, из которых заслуживают внимания языки С, PL/1 и PL/М, точка с запятой используется как признак окончания оператора.) В языке Паскаль после последнего оператора, входящего в составной оператор, точка с запятой не ставится. Тем не менее следующий составной оператор является правильным:
BEGIN
temp := х;
х:=у;
у := temp;
END
Этот составной оператор состоит из четырех операторов, последний из которых является пустым оператором. Лишнюю точку с запятой можно было бы и удалить, однако есть аргумент в пользу ее сохранения. Оставив ее, мы избегнем ошибки, которая могла бы возникнуть при добавлении новых операторов в составной оператор. Однако точку с запятой нельзя без разбора ставить после любого оператора в программе на языке Паскаль — в следующем разделе мы разберем соответствующие примеры.
Условный оператор
Ветвление в программе на Паскале можно реализовать, например, с помощью условного оператора IF, который имеет следующий основной формат:
IF условие THEN оператор
где условием может быть любое булево выражение, а в качестве оператора может использоваться любой простой или структурный оператор. Оператор, входящий в условный оператор, выполняется только тогда, когда условие имеет значение «истинно».
Очень часто в зависимости от некоторого условия приходится производить выбор одного из двух возможных действий. В языке Паскаль это осуществляется путем добавления к оператору IF элемента «ELSE оператор»:
IF условие THEN оператор! ELSE оператор2
Если условие принимает значение «истинно», то выполняется «оператор!», в противном случае реализуется «оператор2». В языке ассемблера или в любом языке, имеющем только элементарный условный оператор, выполняющий проверку условия и передачу управления, оператор IF—THEN—ELSE обычно имеет следующий вид:
IF условие THEN GOTO меткаА;
оператор2;
GOTO меткаВ;
меткаА: оператор;
меткаВ:...;
5—1393
Синтаксическая диаграмма условного оператора показана на рис. 2.11, а на рис. 2.12 приведен пример программы, в которой используется условный оператор. Следует отметить, что, согласно синтаксису (см. синтаксическую диаграмму условного оператора и примеры), перед зарезервированным словом EISE не ставится в качестве разделителя точка с запятой. Судя по приведенному примеру, условные операторы могут быть вложенными. Когда в условный оператор входит составной опера-
Яслобный операторЯслобный операторРис. 2.11. Синтаксическая диаграмма оператора IF.Рис. 2.11. Синтаксическая диаграмма оператора IF.
Яслобный оператор
Яслобный оператор
Рис. 2.11. Синтаксическая диаграмма оператора IF.
Рис. 2.11. Синтаксическая диаграмма оператора IF.
тор, компонентами последнего могут быть и условные операторы. В последней части примера приводится составной оператор IF, в котором после зарезервированного слова ELSE каждого следующего оператора IF, кроме последнего, содержится другой оператор IF. Эта структура является общеиспользуемой.
Рассмотрим оператор IF, в котором после зарезервированного слова THEN записан другой оператор IF:
IF условие! THEN
IF условие2 THEN оператор!
ELSE оператор2;
Поскольку вторая строка данной конструкции напечатана со сдвигом вправо, может показаться, будто ELSE-часть относится к первому оператору IF. Однако, согласно синтаксису языка, ELSE-часть соответствует ближайшему оператору IF, которому еще не «приписана» ELSE-часть. Несмотря на однозначность определения и установления соответствия между компонентами условного оператора, конструкцию THEN IF лучше не употреблять, так как это может вызвать неясность в программе. Возникающую двусмысленность легко устранить либо переходом к противоположному условию и записью «оператор2» непосредственно после первого в конструкции зарезервированного слова THEN, т. е. в THEN-части первого оператора IF, либо путем использования составного оператора для установления желаемого порядка действий. Применяя последний прием, получим IF условие! THEN
BEGIN IF условие2 THEN оператор! END
ELSE оператор2;
PROGRAM Iffy (input,output);
VAR radius, area, diameter : real;
hour, minute, number : integer;
inchar : char;
BEGIN
{Find the radius and diameter of a circle of a given area.} read (area);
IF area >= 0 THEN
BEGIN
radius := sqrt(area/3.1416);
diameter := 2*radius;
writeln('For a circle with area1, area, ’the radius is’, radius, ’and the diameter is’, diameter)*;
END
ELSE writeln('Negative area (', area, ') not allowed’);
(Convert 24-hour time to 12-hour time.}
read (hour,minute);
IF (hour < 0) OR (hour > 23)
OR (minute < 0) OR (minute > 59) THEN
BEGIN {if input out of range, set to default.} hour := 0; minute := 0;
END;
IF hour <= 11 THEN*
" BEGIN
IF hour = 0 THEN hour := 12;
writeln('Time is', hour, ’:', minute, 'AM'); END
ELSE
BEGIN
hour := hour - 12;
IF hour = 0 THEN hour := 12;
writeln ('Time is'* hour, ':', minute, 'PM'); END;
[Read a hex-digit character and convert to an integer.}•
{Assumes ASCII ordering of characters.}
number := -1;
read (inchar);
IF (inchar >= '0') AND (inchar <= '9') THEN number := ord (inchar) - ord('O')
ELSE IF (inchar >= 'a') AND (inchar <= 'f) THEN
number := ord(inchar) - ord(’a') + 10
ELSE IF (inchar >= 'A') AND (inchar <= 'F') THEN
number := ord(inchar) - ord(’A') + 10
ELSE writeln(inchar, ' — not a hex character');
writeln('The decimal value of ', inchar, ' is', number); END.
Рис. 2.12. Пример программы с использованием условного оператора IF.
Б*
Оператор цикла
Оператор цикла FOR представляет собой средство выполнения некоторого оператора с заданным количеством повторений (циклов); этот оператор имеет следующую общую формуз FOR параметр := выражение! ТО выражение2 DO оператор
Здесь параметр, или управляющая переменная цикла, является обычной описываемой переменной, а входящие в рассматриваемый оператор выражения должны быть простого типа, но не вещественного. «Выражение!» и «выражение2» вычисляются в начале выполнения оператора FOR, параметру цикла присваивается значение выражения 1. Затем значение параметра цикла сравнивается со значением выражения 2; если отношение (параметр>выражение2) примет ложное значение, то оператор выполнится еще один раз, т. е. параметр примет следующее значение и описанный выше процесс повторится. Если же указанное отношение примет значение «истинно», то выполнение оператора цикла прекратится. Используя элементарный условный оператор, выполнение которого заключается в проверке условия и передаче управления, можно записать следующую последовательность операторов, эквивалентную рассматриваемому оператору цикла: параметр := выражение!;
переменная := выражение2;
меткаА: IF (параметр>переменная) THEN GOTO меткаВ; оператор;
параметр := succ (параметр);
GOTO меткаА;-
меткаВ:...;
Здесь переменная имеет то? же тип, что и выражения 1 и 2. Только в одном отношении эта последовательность не эквивалентна оператору цикла языка Паскаль: Гв соответствии со стандартом языка Паскаль значение параметра цикла не определено после завершения цикла даже в том случае, если для выхода из цикла используется оператор GOTO.
Зарезервированное слово ТО в операторе цикла можно заменить словом DOWNTO, при этом параметр цикла будет изменяться от больших значений к меньшим. В этом случае следующее значение параметра вырабатывается функцией pred (параметр), а условием окончания цикла является значение отношения (параметрапеременная). На рис. 2.13 представлена синтаксическая диаграмма для обоих типов оператора цикла.ч
Каждое выполнение оператора, входящего в оператор цикла, называется итерацией цикла.. Так как границы изменения
параметра цикла определяются значениями выражения 1 и выражения 2, число итераций может быть разным для каждого нового выполнения оператора цикла, несмотря на то что это число
Рис- 2.13. Синтаксическая диаграмма оператора FOR.
PROGRAM FourFORloops (input, output).;
VAR
power, n, m, nsum : integer;
charl, char2, pchar : char;
innum, sum : real;
BEGIN
{Print a table of integers and squares»} FOR i := 1 TO 10 DO writein (i, i*i) ;
{Print a table of powers of 2.}
power := 1;
FOR i : = 0 TO 10 DO
BEGIN writein(i,power); power := power*2 END;
{Read two characters and print all the ones between.} readln(char 1,char2) ;
IF charl < char2 THEN
FOR pchar := charl TO char2 DO writein(pchar)
ELSE FOR pchar := charl DOWNTO char2 DO writein(pchar);
{Compute sums of n groups of input numbers.} read (n); {Number of groups to be summed.} FOR nsum := 1 TO n DO
BEGIN
sum := 0; read (m); {Number of components in this group.) FOR i := 1 TO m DO
BEGIN read(innum); write(innum); sum := sum+innum END; writein (sum);
END;
END.
Рис. 2.14. Пример программы с использованием операторов FOR.
устанавливается после того, как началось конкретное выполне- ние оператора цикла. Кроме того, поскольку параметр цикла проверяется перед началом выполнения каждой итерации, возможно, что оператор, содержащийся в операторе цикла, ни разу не выполнится. Пример программы, в которой использованы операторы цикла, представлен на рис. 2.14.
СТРУКТУРНЫЕ ОПЕРАТОРЫ
(ГРУППА II)
Нет особых причин для введения отдельного раздела, посвященного трем структурным операторам. Это объясняется лишь тем, что предыдущий раздел мог оказаться слишком громоздким.
Оператор WHILE
В отличие от оператора FOR, каждое выполнение которого начинается с определения числа циклов, при реализации оператора WHILE (ПОКА) количество повторений не определяется заранее, а зависит от вычислений, производимых в самом операторе WHILE. Оператор WHILE имеет следующую общую форму:
WHILE условие DO оператор
Синтаксическая диаграмма этого оператора представлена на рис. 2.15.
Условием в операторе WHILE может быть любое булево выражение, а в качестве оператора можно использовать любой
ОператорОператорОператор
Оператор
Оператор
WHILE
Рис. 2.15. Синтаксическая диаграмма оператора WHILE.
оператор. Оператор WHILE определяет логическое значение условия, и если оно имеет значение «истинно», то производится выполнение оператора, входящего в оператор WHILE; этот процесс повторяется до тех пор, пока сохраняется значение «истинно». Очевидно, что значения переменных, входящих в условие, должно изменяться во время выполнения оператора WHILE, иначе он никогда не будет завершен. В программе, приведенной на рис. 2.16, содержится несколько примеров применения оператора WHILE. Этому оператору эквивалентна следующая последовательность операторов:
мегкаА : IF NOT условие THEN GOTO меткаВ;
оператор;
GOTO меткаА;„
меткаВ:...;
Совершенно очевидно, что если перед выполнением оператора WHILE проверяемое в нем условие будет иметь значение «ложно», то цикл не выполнится ни разу. Кроме того, следует
PROGRAM Whilst (input,output);
VAR n, power, nnums, hour : integer; innum, sum : real;
BEGIN
{Print a table of powers of 2.}
n := 0; power := 1;
WHILE power < (maxint DIV 2) DO
BEGIN writein (n,power) ; n := n+1; power := power*2 END;
writein(n,power); {Print last table entry.}
{Average a sequence of nonnegative numbers.) sum := 0; nnums := 0;
write(’Input sequence:’); read (innum);
WHILE innum >= 0 DO {Any negative number terminates sequence.} BEGIN sum := sum + innum; nnums := nnums + 1; write(innum); read(innum);
END;
writein; writein (nnums, ’numbers, average is’, sum/nnums);
{While away the hours.)
read (hour); {Out-of-range hour terminates loop.)
WHILE (hour >= 0) AND (hour <= 23) DO BEGIN
IF hour = 0 THEN writein ('Hour 0 is midnight’).
ELSE IF hour < 12 THEN
writein (’Hour', hour, ’is’, hour, ’AM’)
ELSE IF hour = 12 THEN writeln('Hour 12 is noon’)
ELSE writein (’Hour’, hour, ’is’, hour-12, ’PM’);, read (hour);
END;
END.
Рис. 2.16. Пример программы с использованием операторов WHILE.
отметить, что оператор WHILE имеет сходство с оператором FOR. Действительно, оператор FOR можно смоделировать, используя для этого оператор WHILE:
параметр : = выражение!;
переменная : = выражение2;
WHILE параметр<_ = переменная DO
BEGIN
оператор;
параметр : = succ (параметр);
END;
Однако во всех подходящих случаях следует применять оператор FOR, так как написанные с его использованием программы легче читать, а присущие ему ограничения предоставляют компилятору большие возможности для оптимизации программы.
Оператор REPEAT
Оператор REPEAT (ПОВТОРЕНИЕ), синтаксическая диаграмма которого представлена на рис. 2.17, действует подобно .оператору WHILE, но при его выполнении проверка условия производится после каждой итерации. /Отметим три основных отличия оператора REPEAT от оператора WHILE: оператор REPEAT обеспечивает выполнение по крайней мере одного цикла; когда логическое значение условия «ложно», выполнение
ОператорREPEATОператорREPEATРис. 2.17. Синтаксическая диаграмма оператора REPEAT.Рис. 2.17. Синтаксическая диаграмма оператора REPEAT.
Оператор
REPEAT
Оператор
REPEAT
Рис. 2.17. Синтаксическая диаграмма оператора REPEAT.
Рис. 2.17. Синтаксическая диаграмма оператора REPEAT.
цикла продолжается; цикл может содержать не один, а несколько операторов. Последнее отличие связано с тем, что компилятор воспринимает зарезервированное слово REPEAT так, будто вместо него записана пара слов REPEAT BEGIN, а слово UNTIL он идентифицирует как END UNTIL.
Оператору REPEAT соответствует следующая последовательность операторов:
меткаА: оператор;
IF NOT условие THEN GOTO меткаА;
Данная последовательность немного проще, чем последовательность, эквивалентная оператору WHILE, поскольку в операторе REPEAT условие окончания цикла проверяется в конце цикла.
Оператор WHILE используется чаще, чем оператор REPEAT; это связано с тем, что во многих случаях желательно работать с таким оператором цикла, для которого предусмотрена возможность функционирования с нулевым числом циклов, т. е. когда он не выполнится ни разу. На рис. 2.18 представлена программа, в которой содержатся два примера целесообразного использования оператора REPEAT. В примере вычисления степеней 2 за счет применения оператора REPEAT удается написать более простую программу по сравнению с аналогичным примером, в котором использовался оператор WHILE (см. программу на рис. 2.16). Во втором примере необходимо использовать оператор REPEAT, потому что в данном случае проверка уело-
вия окончания возможна только после одного выполнения цикла.
Иногда выполнение любого из рассмотренных операторов цикла необходимо прервать в связи с проверкой некоторого нестандартного условия окончания цикла. Часто бывает невозможно свести все нестандартные условия окончания к одному общему условию окончания цикла, особенно когда ситуации, при которых выполняются эти условия, возникают только в середине цикла. Простым способом прерывания выполнения цик-
PROGRAM Repetitious (input,output); VAR
n, power : integer;
inchar : char;
BEGIN
{Print a table of powers of 2.}
n : = 0; power := 1; REPEAT
n := n + 1; power := power * 2; writein (n,power);
UNTIL power > (maxint DIV 2);
{Read and print a sentence.} REPEAT
read (incha’r) ; write (inchar) ;
UNTIL inchar = 1.•;
END.
Рис. 2.18. Пример программы, в которой используется оператор REPEAT.
ла при наличии нестандартного условия является использование оператора GOTO для каждого такого условия. Поскольку, согласно принципам структурного программирования, не рекомендуется использовать оператор GOTO, в некоторых версиях языка Паскаль предусмотрен оператор EXIT, выполнение которого приводит к завершению цикла.
Оператор выбора
Оператор выбора CASE можно рассматривать как более общий Случай условного оператора. В то время как условный оператор в зависимости от значения булева выражения обеспечивает выполнение одного из двух операторов, оператор выбора, согласно значению, относящемуся к перечислимому или ограниченному типу, производит выбор из нескольких операторов. Синтаксическая диаграмма оператора выбора представлена на рис. 2.19. Селектором в операторе выбора является выражение, значение которого принадлежит целочисленному, литерному или булеву типу, либо типу, определяемому программистом,— пе-
речислимому или ограниченному. Оператор выбора имеет CASE-список с элементами, представляющими собой оператор, перед которым записывается одна или несколько констант, отделяемых от оператора двоеточием. Эти константы должны иметь такой же тип, как селектор. Для выполнения будет выбран тот оператор, перед которым стоит константа, равная значению селектора. Ниже приведен пример оператора выбора, в
Рис. 2.19. Синтаксическая диаграмма оператора CASE.
котором селектор может иметь одно из четырех значений, а CASE-список состоит из трех элементов:
TYPE myCases=1..4;
VAR which : my Cases;
CASE which OF
: write ('This is case 1');
: write ('But this is case 2');
,4 : write ('This could be either case 3 or case 4');
END;
Точку с запятой после последнего элемента списка можно не ставить. Каждое отличное от других значение селектора может появляться в качестве константы только в одном элементе CASE-списка. [Желательно, чтобы любое возможное значение селектора, имеющего перечислимый или ограниченный тип, было указано в некотором элементе CASE-списка, так как нельзя предсказать поведение оператора выбора в случае, когда селектор примет значение, не совпадающее ни с одной константой CASE-списка. Однако, согласно стандарту языка Паскаль, предложенному Институтом инженеров по электротехнике и радиоэлектронике, возникновение такой аномальной ситуации должно сопровождаться выработкой сообщения об ошибке во время выполнения программы.! Чтобы составить, используя
Язык программирования Паскаль 75 только элементарный условный оператор, выполняющий про- верку условия и переход, фрагмент программы, эквивалентный оператору выбора, необходимо написать столько элементарных условных операторов, сколько элементов содержится в CASE-
PROGRAM ClassifyOrClassylf (input,output);
|Эта программа вводит строчные буквы и классифицирует их согласно определенным характеристикам. Предполагается, что вводимые литеры представлены в коде ASCII. Таким образом, отрезок ’а’ . . ’z* содержит только строчные буквы. Программа не может работать с другими наборами литер}
TYPE letter = 'a'..'z';
VAR inchar : letter;
vowel, hexdigit, dull, descender, multistroke : boolean;
BEGIN
vowel := false; hexdigit := false; dull := false;
descender := false; multistroke := false;
read(inchar);
CASE inchar OF
’a','e' : BEGIN vowel := true; hexdigit := true END;
’i' : BEGIN vowel := true; multistroke := true END;
'o','u' : vowel := true;
•y* : BEGIN vowel := true; descender := true END;
'b','c','d',1f’ : hexdigit := true;
'g'r'p'f'q* 2 descender := true;
•j* : BEGIN descender := true; multistroke := true END;
’t','x' : multistroke := true;
'h','k','1','m','n','r','s','v','w','z' : dull := true;
END;
writeln (inchar, 1 has the following properties:1);
IF vowel THEN writeln(’It is a vowel.*)
ELSE writeln(’It is a consonant.1);
IF hexdigit THEN writeln(’It is a hexadecimal digit.*);
IF descender THEN writeln ('It has a descender.*);
IF multistroke THEN
writeln('You must lift your pen to write it in script.*);
IF dull THEN writeln('It is a very boring character.');
END,
Рис. 2.20. Пример программы с использованием оператора выбора.
списке. Что касается оператора выбора, то, как будет показано при рассмотрении различных типов микро-ЭВМ (часть III книги), он может быть в большинстве случаев эффективно реализован с помощью таблицы переходов.
Многие задачи поддаются декомпозиции — для программной реализации таких задач очень удобен оператор выбора. Кроме того, оператор выбора часто используется для обработки команд и классификации объектов. На рис. 2.20 приведен пример программы, в которой использован оператор CASE.
УПРАЖНЕНИЯ
Каковы преимущества применения определяемых констант по сравнению с использованием одноименных с определяемыми константами переменных, которым в начале программы присваиваются постоянные значения?
Предполагая, что используемые в данном упражнении переменные того же типа, что и одноименные переменные, описанные в программе, представленной на рис. 2.9, объясните, может ли быть выполнено компилирование следующих операторов присваивания, среди которых есть операторы, содержащие ошибки:
k := j * i + 5;
j := (i + к)/2;
b := a*c/1.1E10;
i := (j + m) MOD k£
к : = ord (inChar) ;
done := (a = 2);
valid := valid OR (j=k) OR NOT done; a := b * с - к + succ('a');
outChar := pred (inChar) ;
Напишите фрагмент программы, эквивалентный нижеприведенному, не применяя при этом вложенных операторов IF.
IF х>=0 THEN
BEGIN
IF x<=10 THEN
BEGIN
IF x=0 THEN result := O' ELSE result :» x;
END
ELSE result := sqrt(x)
END
ELSE IF x<-20 THEN result sqrt(-x+10)
ELSE result := -x;
В программе, представленной на рис. 2,12, устанавливаются нулевые значения переменных, отображающих часы и минуты, когда любая из этих двух величин выходит за диапазон допустимых значений. Измените программу так, чтобы при нарушении диапазона допустимых значений времени выдавалось сообщение об ошибке, а для тех случаев, когда обе вводимые величины находятся в допустимых пределах, время выводилось на печать.
Измените программу, представленную на рис. 2.12, таким образом, чтобы число выводилось на печать только тогда, когда введенная литера является шестнадцатеричной цифрой.
Найдите простой оператор, эквивалентный следующему условному оператору:
IF х>у THEN greater := true ELSE greater := false;
Сумматор, программа которого представлена на рис. 2.3, .не может выполнять действий, когда либо прибавляемое число, либо накопленная сумма превысят константу maxint Измените программу так, чтобы она обнаруживала переполнение и печатала бы соответствующее сообщение.
Напишите фрагмент программы, эквивалентный следующему ниже, без использования операторов GOTO:
IF х=1 THEN GOTO 1;
IF х=2 THEN GOTO 2;
IF x=3 THEN GOTO 3;
IF x=4 THEN GOTO 4;
5: оператора;
операторЬ;
операторе;
операторе!;
1: операторе;
Эквивалентны ли следующие два оператора? Дайте обоснование вашего решения.
FOR i := 1 ТО n DO
IF prod < (maxint DIV i) THEN prod :» prod * i ELSE n := i;
BEGIN
i := 1;
WHILE i <= n DO
BEGIN
IF prod <' (maxint DIV i) THEN prod ?® prod * i ELSE n := i;
i := i + 1;
END;
END; .
Напишите значения переменных, которые будут выведены на печать при выполнении двух первых циклов программы, представленной на рис. 2.14.
Предполагая, что maxint=215—1, напишите таблицы степеней двух, которые были бы выведены на печать при выполнении программ, представленных на рис. 2.16 и 2.18.
Эквивалентны ли следующие два оператора? Дайте обоснование вашего решения.
WHILE условие! DO
WHILE условие2 DO
оператор;
WHILE условие! AND условие2 DO оператор;
Приведите общую форму представления оператора REPEAT эквивалентной последовательностью с использованием оператора WHILE.
Укажите общую форму оператора CASE, эквивалентного оператору IF.
Исключите из программы, представленной на рис. 2.20, оператор read и воспользуйтесь оператором FOR для упорядочения строчных букв.
Для инженеров по радиотехнике. Напишите последовательность операторов языка Паскаль, которая обеспечит вычисление сопротивления резистора, предполагая при этом, что цветовой код представлен значениями трех переменных (stripe 1, stripe2 и stripe3), относящихся к ограниченному типу colorCode.
ЛИТЕРАТУРА
С созданным Паскалем сумматором и со многими другими изобретениями знакомит книга Голдстайна (Goldstine Н., The Computer from Pascal to von Neumann, Princeton University Press, Princeton, NJ, 1972). В этом труде содержится богатый исторический материал, приведены ссылки на литературные источники. *
Первоначальное определение стандарта языка Паскаль дано Дженсеном и Виртом (Jensen К., Wirth N., Pascal User Manual and Report, Springer-Ver- lag, 1978, 2nd. ed.). Указанное руководство предназначено для программистов, работающих с языком Паскаль. Проект стандарта языка Паскаль, подготовленный Обществом по вычислительной технике при Институте инженеров по электротехнике и радиоэлектронике, был опубликован в журнале Computer (Ravenel В. W., Toward a Pascal Standard, 12, 4 (April 1979), pp. 68—92). Усилия, предпринимаемые в направлении стандартизации языка Паскаль, и сейчас освещаются на страницах этого журнала.
По языку Паскаль выпущен ряд пособий, среди которых можно отметить два хороших вводных курса (Schneider G. М., Weingart S. W., Perlman D. М., An Introduction to Programming and Problem Solving with Pascal, Wiley, 1978; Grogono P.. Programming in Pascal, Addison-Wesley, 1978. [Имеется перевод: Грогоно П. Программирование на языке Паскаль. — М.: Мир, 1982]). Эти книги отличаются полнотой и ясностью изложения, содержат тщательно подобранный список литературы. Кроме того, заслуживает внимания рассчитанная на широкую читательскую аудиторию работа Боулеса (Bowles К., Problem Solving Using Pascal, Springer-Verlag, 1977). В книге Вирта (Wirth N., Algo- rithms+Data Structures=Programs, Prentice-Hall, 1976) изложены основные методы программирования и структурирования данных с использованием языка Паскаль.
33
3
3
СТРУКТУРЫ ДАННЫХВ ПРОГРАММАХНА ЯЗЫКЕ ПАСКАЛЬ
Н. Вирт — создатель языка Паскаль — является автором учебника по программированию под названием «АлгоритмыЧ- Структуры данных=Программы». Ознакомившись с предыдущими главами, мы составили представление о том, что является программами и алгоритмами. Теперь, чтобы выяснить, что представляют собой структуры данных, достаточно из «программ» вычесть «алгоритмы», и... настоящую главу можно не читать. ■ ' '
Если же говорить серьезно, то «равенство Вирта» указывает на основополагающее значение и важность структур данных в программировании. Структуру данных можно определить как совокупность связанных данных и множество правил, определяющих их организацию и способ доступа к элементам данных. Список членов семьи является примером простой структуры данных; родословное дерево представляет собой пример более сложной структуры данных.
Выбор структуры данных влияет на то, каким образом мы можем использовать эти данные. Располагая списком членов семьи или родословным деревом, можно упорядочить фамилии и имена по алфавиту, определить, присутствует ли данное лицо в списке членов семьи, отыскать тех, кто носит короткую или длинную фамилию, и т. п. С помощью родословного дерева можно определить такие понятия родства, как брат (сестра), двоюродный брат (сестра), отец (мать), супруг (супруга) и др., и установить количество поколений.
Две различные структуры данных могут содержать одинаковую информацию; выбор той или иной структуры данных, определяемый ее назначением, влияет на эффективность доступа к данным. Например, телефонная компания предлагает своим абонентам телефонную книгу, в которой фамилии абонентов упорядочены по алфавиту. Кроме того, те же самые данные упорядочиваются по адресам абонентов, что позволяет быстро находить злоумышленников, досаждающих вызовами.
Еще одним фактором, учитываемым при выборе структур данных, является объем памяти, необходимый для организации структуры данных. Предположим, требуется хранить в памяти все простые числа, не превышающие число 1000. Количество чисел составляет 168; расходуя на каждое число по одному слову памяти, следует выделить 168 слов. В ЭВМ с 16-разрядными словами общий объем необходимой для хранения чисел памяти составил бы 2688 бит. С другой стороны, можно выделить 1000 бит памяти, считая, что каждому целому числу в диапазоне 1—1000 соответствует 1 бит. Каждый бит, соответствующий простому числу, будет иметь в «битовом отображении» значение 1, всем остальным числам будут соответствовать значения битов, равные 0.
В настоящей главе обсуждаются вопросы о том, как в языке Паскаль определяются структуры данных и каким образом можно манипулировать ими. В части III книги будут рассмотрены наборы команд некоторых типов микро-ЭВМ и будет показано, как влияют наборы команд на способы организации структур данных и на приемы их использования в программах, написанных на языке ассемблера.
ОДНОМЕРНЫЕ МАССИВЫ
Наиболее простым и распространенным типом структуры данных является массив. Массив — это упорядоченный список элементов данных одного типа. К элементу одномерного массива можно обратиться посредством указания имени массива и значения одного индекса. Простой пример одномерного массива, состоящего из десяти целых чисел, представлен в табл. 3.1.
Таблица 3.1. Массив ints, содержащий десять целых чисел
Элемент массива
Шестнадцатеричный адрес в памяти
Значение
ints
ГГ
2300
119
ints
[2*
2302
—17
ints
I3'
2304
—88
ints
X
2306
24 .
ints
51
2308
0
ints
6'
230А
27
ints
V
230С
—99
ints
'8s
230Е
32
ints
9s
2310
6
ints
10
1
2312
—51
Примечание. Предполагается, что каждое целое число занимает 2 байт в памяти ЭВМ с побайтовой адресацией.
Таблица 3.2. Характеристики массивов в различных языках высокого уровня
Язык
Индекс первого элемента
Индекс последнего элемента
Многомерные массивы
Динамические массивы
Алгол
Описывается
Описывается
Используются
Используются
Бейсик
0 или 1
Описывается или устанавливается по умолчанию
>
Не используются
Фортран
1
Описывается
»
»»
Паскаль
Описывается
»
»
»»
PL/1
Описывается или устанавливается по умолчанию
»
»
Используются
PL/M
0
»
Не используются
Не используются
PL/Z
0
»
Используются
»»
Очевидно, что элементы массива расположены в следующих друг за другом ячейках памяти. Одномерный массив иногда называют вектором.
Основными характеристиками массива являются: имя массива, тип его элементбв и индексы первого и последнего элементов массива. При составлении программ на языках высокого уровня не задают фактические адреса ячеек памяти, используемых для размещения массива; на каждый элемент массива можно сослаться, указав имя массива (в рассматриваемом нами случае именем является ints) и значение индекса, например [3], который определяет позицию элемента в массиве. Различные языки высокого уровня накладывают на массивы разные ограничения (табл. 3.2). В одних языках индекс первого элемента массива фиксирован, в других языках допускается его произвольное значение. В некоторых языках предусмотрена работа с динамическими массивами, в которых начальное и конечное значения индексов не фиксируются при написании программы, а определяются либо посредством ввода данных, либо даже путем вычислений, предусматриваемых в программе. В большинстве языков имеется возможность использования многомерных массивов.
Чтобы убедиться в полезности задания значения первого индекса массива, рассмотрим 100-элементный массив, содержащий ежегодную величину валового национального продукта в XIX в. В языке Паскаль, который допускает задание начального и конечного значений индекса, такой массив может быть объявлен следующим образом:
VAR gnp : ARRAY [1801..1900] OF real;
6—1393
Используя язык, в котором начальное значение индекса массива принимается равным 1, пришлось бы воспользоваться одним из следующих двух (менее желательных, чем в приведенном выше примере) способов представления данных о национальном продукте. Во-первых, можно было бы описать массив biggnp из 1900 элементов, первые 1800 элементов которого останутся свободными. Во-вторых, можно описать 100-элементный массив shfgnp и для получения значения валового национального продукта за определенный год обратиться к элементу массива shfgnp [index], предварительно вычислив значение индекса (index : = year—1800).
Массив является простейшим из определенных в языке Паскаль структурных типов данных. Формат спецификации диапазона значений индекса в приведенном выше примере тот же, что и формат спецификации ограниченного типа. В Паскале описание переменной, являющейся массивом, имеет следующую общую форму:
VAR
идентификатор : ARRAY [гид индексов-^ OF тип элементов;
где тип индексов может быть любым перечислимым или ограниченным типом (заметим, что в качестве типа индексов нельзя использовать целочисленный или вещественный типы данных), а тип элементов может быть любым. В приведенных ниже описаниях переменных использованы типы, которые были определены в разд. 2.6.
VAR
population : ARRAY [city] OF integer;
opposite: ARRAY [direction] OF direction;
digitCount: ARRAY [digit] OF integer;
paintSupply : ARRAY [color] OF real;
Кроме того, в языке Паскаль программист имеет возможность определять тип массива. Типы массивов определяются в программе в разделе определения типов; переменные, являющиеся массивами, могут описываться, как показано в следующих примерах, путем ссылки на соответствующий определенный тип:'
TYPE
vector=ARRAY [1..50] OF real;.
cityNums=ARRAY [city] OF integer;
- mapLinks=ARRAY [direction] OF direction;
VAR;
a, b, c : vector;
population, elevation : cityNums;
opposite, elkwise, cclkwise: mapLinks;
Строки литер в языке Паскаль представляются массивами. Например, строка текста, предназначенная для отображения на терминале, может быть описана следующим образом:
VAR
line: ARRAY [1..80] OF char;
При объявлении массивов в Паскале можно использовать зарезервированное слово PACKED, с помощью которого компи-
PROGRAM Normalize (input,output);
^Программа вводит экзаменационные оценки и размешает их .в памяти, отведенной под массив. Она вычисляет и печатает значение средней оценки, нормализует все оценки в соответствии о наивысшей и выводит на печать нормализованные оценки}
CONST maxscores = 100; {maximum number of scores}
TYPE idnum = 1..maxscores;
VAR id, nscores : idnum;
score : ARRAY [idnum] OF real;
sum, hiscore, avg : real;
BEGIN
read(nscores); {Read number of scores for this run.} writein(’Number of scores for this run: ’,nscores); sum := 0; hiscore := 0;
FOR id := 1 TO nscores DO
BEGIN
read (score[id]); {Read score} sum := sum + score[id]; {Get sum for average.} IF score [id] > hiscore THEN
hiscore := score[id]; {Get highest score for normalization.} END;
writein (’Highest score is: ’,hiscore);
avg := sum/nscores; {Compute average and print results/}
writein(’Average score is: ’,avg);
writein(’Normalized average is: ’,avg/hiscore);
writein (’Input and normalized scores are: ’);
FOR id:=1 TO nscores DO {Print normalized scores.} writein (id,score[id],score[id]/hiscore);
END..^
Рис. 3.1. Пример программы на Паскале, в которой используется одномерный массив.
лятору сообщается о необходимости располагать данные в памяти ЭВМ настолько эффективно в смысле расхода памяти, насколько это возможно, например, можно дать такое объявление массива:
VAR
line : PACKED ARRAY [L. 80] OF char;
Использование зарезервированного слова PACKED не должно оказывать влияния на результат выполнения программы, тем
не менее в некоторых случаях употребление этого слова сказывается как на размере программы, так и на времени ее выполнения. Благодаря «упаковке», очевидно, уменьшается потребность в объеме памяти для хранения данных, но из-за включения в программу дополнительных команд, предназначенных для упаковки и распаковки данных, возрастает размер программы и время ее выполнения.
На рис. 3.1 представлена программа на Паскале, предназначенная для нормализации экзаменационных оценок, полученных студентами, и для нахождения средней оценки. Оценки должны быть организованы в массив, и нормализация каждой оценки может быть выполнена после определения наивысшей оценки. Так как в языке Паскаль не допускается использование динамических массивов, потребуется объявить в программе массив с числом элементов, равным наибольшему ожидаемому количеству студентов, экзаменационные оценки которых подлежат обработке. При каждом прогоне программы осуществляется ввод количества оценок, которое используется для управления ходом выполнения программы, и ввод самих оценок.
Теперь следует сказать несколько слов относительно контроля в программе входных данных. В обычной практике программирования признаком неприемлемости входных данных для некоторой программы служит аномальное выполнение программы или получение неверных результатов. Обычно это связано с нахождением значений входных данных вне заданного диапазона. Программа, представленная на рис. 3.1, завершится аномально, если количество оценок nscores превысит 100 либо окажется равным или меньшим 0. Например, если nscores больше 100, то во время выполнения программы будет сделана попытка обращения к элементам массива, лежащим вне описанных границ. Если бы переменные id и nscores относились к целочисленному типу, то компилятор с языка Паскаль генерировал бы программу, в которой значения указанных величин сравнивались бы с границами массива при каждом обращении к его элементам. Ограничение значений этих переменных диапазоном 1... 100 позволяет выполнять более эффективный контроль: проверки следует проводить только тогда, когда переменной id или nscores присваивается новое значение. В программах на языке ассемблера такой контроль всегда предусматривается программистом.
МНОГОМЕРНЫЕ МАССИВЫ
Хотя в ЭВМ 'используется одномерная память, в программах могут описываться многомерные массивы, которые определенным образом отображаются в одномерной памяти. Рассмот-
рим, например, матрицу А, состоящую из 3X2 элементов:
1 2
Д =Д =3 4
Д =
Д =
5 6
Существуют два очевидных способа расположения элементов матрицы А в памяти. Можно разместить в памяти строку за строкой, формируя при этом последовательность 1, 2, 3, 4, 5, 6; в таком случае говорят, что матрица записана в памяти по строкам. Иным способом расположения элементов является организация массива в памяти по столбцам, что привело бы к последовательности 1, 3, 5, 2, 4, 6.
В языке Паскаль можно описать многомерный массив как одномерный массив, элементами которого являются массивы. Это означает, что упомянутую выше матрицу 3X2 можно описать как одномерный массив с тремя элементами типа row — строка, каждая из которых в свою очередь является 2-элементным одномерным массивом: TYPE
row=ARRAY [1..2] OF integer;
a3by2array=ARRAY [1..3] OF row;
VAR a: a3by2array;
При последовательной записи каждой строки матрицы в память соблюдается правило размещения двумерного массива «по строкам». Ссылка на элемент матрицы А, лежащий на пересечении i-й строки и /-го столбца, имеет вид a[i][j].
Приведенное выше описание массива можно записать в более краткой форме посредством включения определения типа row в определение типа a3by2array. При этом получим следующее определение типа:
TYPE a3by2array=ARRAY[1..3] OF ARRAY [1..2] OF integer;
Синтаксис языка Паскаль позволяет давать еще более краткое описание, в котором указываются имя массива и диапазоны изменения индексов для каждой размерности массива, например
TYPE a3by2array=ARRAY [1..3, 1..2] OF integer;
Если массив используется один раз, то тип массива можно не снабжать именем, таким образом, мы можем написать
VAR а : ARRAY [1..3, 1..2] OF integer;
Кроме того, ссылка на элемент массива может быть дана в более краткой форме; так, вместо a[i}[j] можно написать a[i,j]. Синтаксис описания массивов очевидным образом рас-
PROGRAM Average (input,output)
{Согласно этой программе вводятся четыре экзаменационные оценки каждого студента и размешаются в памяти, выделенной под массив. Программа предназначена для вычисления и вывода на печать средней оценки каждого студента по четырем экзаменам, средней оценки группы по каждому экзамену и средней оценки всех студентов по всем экзаменам)
CONST
maxstudents = 100; {Maximum number of students handled} nExams = 4; {Number of exams}
TYPE
idNum = 1..maxStudents;
examNum = 1..nExams;<
VAR
id, nStudents : idNum;
exam : examNum;
score : ARRAY (idNum,examNum] OF real;
sum, average : real;
BEGIN read (nStudents); {Read number of students for this run.} FOR id := 1 TO nStudents DO
FOR exam := 1 TO nExams DO read (score[id,exam]); {Read all scores.} writein(’Average for each student: •);
FOR id := 1 TO nStudents DO {Get average for each student.) BEGIN
sum := 0;
FOR exam := 1 TO nExams DO sum := sum + score[id,exam];
average := sum/nStudents;
writein(id,average);
END;
writein(’Average for each exam: ’);
FOR exam := 1 TO nExams DO {Get average for each exam.} BEGIN
sum := 0;
FOR id := 1 TO nStudents DO sum := sum + score[id,exam];
average := sum/nExams;
writein (exam,average);
.END;
sum := 0;
FOR exam := 1 TO nExams DO {Get average for all exams.}
FOR id := 1 TO nStudents DO sum .:= sum + score [id,exam]; average := sum/(nExams*nStudents); writein(’Average for all exams: ’,average);
END.
мерный массив.^ программы на языке Паскаль, в которой используется дву-
пространяется на массивы с тремя и большим числом измерений; например, массив 5X19X6 объявляется следующим образом:
VAR b : ARRAY [0..4, 2..20, Ю.ЛбрСР real;
Один из вариантов использования многомерного массива иллюстрирует программа, представленная на рис. 3.2. Эта программа на Паскале предусматривает ввод четырех экзаменационных оценок для каждого студента группы, число студентов в которой нс должно превышать 100, и размещение оценок в двумерном массиве. Затем, согласно программе, вычисляется средняя оценка группы по каждому экзамену, средняя оценка каждого студента по четырем экзаменам и определяется общая средняя оценка группы по всем четырем экзаменам.
СТЕКИ
Стеком называется одномерная структура данных, добавление элемента в которую (или исключение элемента из которой) производится с одного конца, называемого верхушкой стека. Стек работает по принципу LIFO (last-in, first-out) —«поступивший последним обслуживается первым».
Пример стека, состоящего из блока памяти и переменной sp, называемой указателем стека, приведен на рис. 3.3. В этом примере указатель стека всегда задает свободную ячейку, находящуюся непосредственно над верхним элементом стека. Ниже будет рассмотрен пример организации стека, в котором указатель стека задает непосредственно верхний элемент.
При выполнении операции загрузки элемента в стек данные записываются на место, определяемое указателем стека, а сам указатель стека устанавливается таким образом, что задает следующую свободную ячейку блока памяти. Данные изымаются из стека с помощью операции извлечения элемента из стека, при выполнении которой указатель стека возвращается назад на один шаг.
Стек организован подобно тому, как устроена «корзина с задачами» некоего доведенного до изнеможения ученого. Как только появляется новая задача, текущая задача опускается в корзину, а ученый приступает к выполнению новой задачи. Когда новая задача будет решена, ученый вернется к предыдущей задаче.
Если задач слишком много, некоторые из них могут оказаться утерянными или забытыми. Подобным образом будет переполняться и стек, если в него загрузить слишком много элементов. Такая ситуация возникает вследствие ограниченного размера блока памяти, выделяемого для организации стека. Что
происходит при переполнении стека, зависит от способов его программной реализации. В случае правильно составленной программы стек переполняться не будет, поскольку в такой программе предусмотрено средство обнаружения переполнения
Рис. 3.3. Состояния стека:
а —исходное| б —после загрузки элемента W; в —после загрузки элемента X; е —после загрузки элемента Y; д — после извлечения элемента Y; е — после загрузки элемеж* та Z; ж — после извлечения элемента Z; з —после извлечения элемента X.
При работе со стеком может возникнуть ситуация, которую довольно трудно себе представить, — это попытка извлечения из стека элемента в то время, когда он пуст. Такую ситуацию можно назвать антипереполнением. Если не предусмотреть средства обнаружения антипереполнения, то последствия для программы могут оказаться столь же катастрофическими, как и при возникновении ситуации переполнения стека.
Один из способов реализации стека средствами языка Паскаль состоит в описании одномерного массива stack и использовании переменной sp целочисленного типа в качестве указателя стека. Тип элементов стека может быть любым; так, в нижеследующем примере употребляются элементы стека литерного типа:
CONST stackSize—100;
VAR
stack: ARRAY [L.stackSize] OF char;
sp : integer;
Если в программе используется стек, то в ней должна быть предусмотрена его начальная установка в состояние «пусто». Для нашего примера потребовалось бы написать следующий фрагмент программы:
BEGIN
sp : = 1;
Для загрузки элемента в стек достаточно выполнить следующую последовательность операторов:
{Загрузка элемента х в стек)
stack [sp]: = х;
sp := sp+1;
а для извлечения элемента из стека можно воспользоваться такими операторами:
(Извлечение элемента х из стека}
sp := sp—1;
х : = stack [sp];
Однако если мы попытаемся поместить в стек слишком много элементов, то при выполнении первой последовательности операторов возникнут ошибки в программе. Можно предусмотреть, как показано в следующем примере, специальную проверку на переполнение стека.
(Проверка на переполнение и загрузка элемента х в стек}
IF sp < = stackSize THEN
BEGIN stack [sp] := x; sp := sp+1 END ELSE
(Переполнение, сообщение об ошибке};
Подобным же образом, прежде чем извлечь элемент из стека, следует убедиться в том, что стек не пуст. Такой контроль предусмотрен включением в программу следующей последовательности операторов:
(Проверка наличия элементов и извлечение элемента х из стека}
IF sp>l THEN
BEGIN sp: = sp—1; x: = stack [sp] END
ELSE
{Антипереполнение, сообщение об ошибке};
На рис. 3.4 представлен пример простой программы, в которой стек используется для изменения порядка расположения литер в строке.
В предыдущих примерах указатели стека всегда указывали на ближайшую к верхнему элементу свободную ячейку. Для чтения данных, находящихся в верхушке стека, без удаления
PROGRAM Reverse (input,output)
{Программа предназначена для ввода строки текста, заканчивающейся точкой, и ее загрузки на временное хранение в стек* За-* тем извлекаемые из стека литеры выводятся на печать и стек освобождаетсяj
CONST maxLen = 80; {maximum length of a string} endChar = 1.’; {string termination character}
VAR sp : integer;
stack : ARRAY [1..maxLen] OF char; inChar : char;
BEGIN
sp := 1; read (inChar); writef Input string is; ’);
WHILE inChar <> endChar DO
IF sp <- maxLen THEN
BEGIN (Stack not full.}
stack[sp] ;= inchar; sp :* sp + 1; {Push the char,}
write (inChar); read (inChar); (Print this char and read next.} END
ELSE
BEGIN (Stack full.}
write('l! String too long I!1); (Error message.} inChar : = endChar; (Force a terminator.}
END;
writein; write(1 Reversed string is: •);
WHILE sp > 1 DO (Pop a char and print it.}
BEGIN sp := sp - 1; write(stack[sp]) END;
5ND.
Рис. 3.4. Программа изменения порядка расположения литер в строке на про» тивоположный.
из него элемента не требуется изменять текущее значение указателя стека, например:
{Чтение данных из верхушки стека без извлечения элемента}
х : = stack[sp—1];
Для облегчения чтения данных из верхушки стека желатель-/ но устанавливать указатель стека таким образом, чтобы он определял верхний элемент стека. Кроме того, можно организовать изменение значения указателя стека в противоположном направлении. Для этого начальная установка должна быть проведена так, чтобы указатель стека определял последний элемент массива, используемого для организации стека. Удаление элемента из стека будет сопровождаться увеличением значения
PROGRAM StackSort (input,output): {Вводится последовательность целых чисел в диапазоне от -99 до + 99. Признаком конца последовательности является число, находящееся вне указанного диапазона. Вводимые числа упорядочиваются с использованием двух стеков.stackL и stackH. После обработки последнего числа производится вывод на печать упорядоченной последовательности[
CONST
maxLen = 200; {Maximum length of a series.)
stacksize = 201; {Size needed in worst case.)
minNum = -99; maxNum * +99; {Allowable input range.)
VAR
spL, spH, inNum, nNums : integer;
stackL, stackH : ARRAY [1..stacksize] OF integer;
PROCEDURE PushH (v : integer); {Push v onto stackH.)
BEGIN spH := spH - 1; stackH[spH] := v END;
FUNCTION PopH : integer; {Pop an integer from stackH.)
BEGIN PopH := stackH[spH]; spH := spH + 1 END;
PROCEDURE PushL (v : integer); {Push v onto stackL.)
BEGIN spL := spL - 1; stackL[spL] := v END;
FUNCTION PopL : integer; {Pop an integer from stackL.) BEGIN PopL := stackL[spL]; spL := spL + 1 END;
BEGIN
spL := stacksize; stackL[spL] := minNum-1; {Initialize stacks.} spH := stacksize; stackHjspH] := maxNum+1;
read (inNum); nNums := 1;
writein('Input sequence: ');
WHILE (inNum >= minNum). AND (inNum <= maxNum) DO {Process inputsa} BEGIN
{Make top of stackL <= inNum) WHILE stackL[spL] > inNum DO PushH(PopL); {Make top of stackH >= inNum) WHILE stackH[spH] < inNum DO PushL(PopH); PushL(inNum); {Now we have the right spot for inNum.) nNums := nNums + 1; {Keep track of how many inputs.} write(inNum); {Print inNum.) IF nNums <= maxLen THEN {If no overflow possible...) read (inNum) {...get next inNum.)
ELSE
BEGIN {Otherwise, print error message...) writeln('*** Too many inputs1); inNum := minNum - 1;{...and force termination.)
END;
END;
{Finished all inputs, now move everything into stackH.)
WHILE spL < stacksize DO PushH(PopL);
writein; write ('Sorted sequence: '); {Print contents of stackH.} WHILE spH < stacksize QO write(PopH);
END.
Рис. 3.5. Программа сортировки с использованием стеков.
указателя стека, а загрузка элемента — уменьшением. Для иллюстрации применения стека с указанными особенностями на рис. 3.5 приведена программа, согласно которой выполняются ввод и сортировка последовательности чисел с использованиемуказателя стека, а загрузка элемента — уменьшением. Для иллюстрации применения стека с указанными особенностями на рис. 3.5 приведена программа, согласно которой выполняются ввод и сортировка последовательности чисел с использованиемРис. 3.6. СодержимоеРис. 3.6. Содержимоестеков, используемых в программе сортировки:стеков, используемых в программе сортировки:а — начальное состояние;а — начальное состояние;б—з — после ввода чисел 17, 5, 89, 12, 2, 85, —100.б—з — после ввода чисел 17, 5, 89, 12, 2, 85, —100.
указателя стека, а загрузка элемента — уменьшением. Для иллюстрации применения стека с указанными особенностями на рис. 3.5 приведена программа, согласно которой выполняются ввод и сортировка последовательности чисел с использованием
указателя стека, а загрузка элемента — уменьшением. Для иллюстрации применения стека с указанными особенностями на рис. 3.5 приведена программа, согласно которой выполняются ввод и сортировка последовательности чисел с использованием
Рис. 3.6. Содержимое
Рис. 3.6. Содержимое
стеков, используемых в программе сортировки:
стеков, используемых в программе сортировки:
а — начальное состояние;
а — начальное состояние;
б—з — после ввода чисел 17, 5, 89, 12, 2, 85, —100.
б—з — после ввода чисел 17, 5, 89, 12, 2, 85, —100.
растающем порядке — в верхушке этого стека располагается наибольшее число. В стеке stackH числа располагаются в порядке их убывания — в верхушке этого стека находится наименьшее число. Кроме того, число в верхушке стека stackL всегда либо меньше, чем число в верхушке стека stackH, либо равно ему. Чтобы записать новое число, потребуется передать данные из стека stackL в стек stackH или из стека stackH в стек stackL таким образом, чтобы значение нового числа лежало между значениями чисел, расположенных в верхушках стеков stackL и stackH. Теперь можно поместить новое число в любой стек (в программе для этого использован стек stackL); при этом числа в стеках остаются упорядоченными. Когда все исходные числа будут таким способом обработаны, следует пе-
редать все числа из стека stackL в стек stackH, а затем извлекать одно за другим числа из стека stackH; упорядоченные исходные числа будут появляться в порядке их возрастания.
Структуры данных типа стека успешно применяются в некоторых областях системного программирования. Так, стеки используются при реализации алгоритмов вычисления выражений в компилляторах и интерпретаторах; в них сохраняются промежуточные результаты вычислений. В программах, написанных на языках высокого уровня (например, на языке Паскаль) и имеющих блочную структуру, локальные данные и другая информация используются в стеке. В блочно-структурированных программах, написанных на языках высокого уровня, параметры процедур обычно передаются с использованием стека. Такой же способ передачи параметров иногда применяется и в программах, составленных на языке ассемблера. Кроме того, как будет отмечено ниже, в микропроцессорах применяется аппаратно реализуемый стек, в котором при обращении к подпрограммам и при обработке прерываний сохраняются адрес возврата и информация о состоянии программы. В программах на языке ассемблера в качестве значения указателя стека sp обычно используется абсолютный адрес ячейки памяти, а не индекс элемента массива. .
ОЧЕРЕДИ
Очередь — одномерная структура данных, в один конец которой добавляют элементы, а изымают с другого конца. Как показано на рис. 3.7, для организации структуры такого типа необходимо располагать блоком памяти и двумя переменными, используемыми в качестве указателей начала (head) и конца (tail) очереди; при этом реализуется принцип FIFO (first-in, first-out) — «поступивший первым обслуживается первым».
При выполнении операции добавления элемента в очередь (рис. 3.7) данные записываются в ячейку, определяемую значением переменной tail, а значение переменной tail изменяется таким образом, чтобы она указывала следующую свободную ячейку блока памяти, доступную для организации очереди. Выполнение операции исключения элемента из очереди состоит в чтении данных, местоположение которых определяется значением переменной head, и изменении значения переменной head таким образом, чтобы она указывала следующий элемент очереди. Очередь оказывается пустой, когда значения переменных head и tail равны.
При использовании языка Паскаль очередь может быть описана и установлена в начальное состояние следующим образом:
CONST queueSize=100;
VAR queue : ARRAY [1..queueSize] OF char;
head, tail: integer;
BEGIN
head := 1; tail : = 1;
Как и в стеке, элементы очереди могут принадлежать любому типу данных.
IIIVIVtall —tall —XXtall —tall —head—head—И/И/head—head—бббб22ааСзСз§ §§ §33ееtall —head—tall —head—tail х . head —tail х . head —XWXWаа
I
I
IV
IV
tall —
tall —
X
X
tall —
tall —
head—
head—
И/
И/
head—
head—
б
б
б
б
2
2
а
а
Сз
Сз
§ §
§ §
3
3
е
е
tall —
head—
tall —
head—
tail х . head —
tail х . head —
X
W
X
W
а
а
Рис. 3.7. Состояния очереди:
а — исходное; б — после добавления элемента W; в — после добавления элемента X; г—после добавления элемента Y; д — после исключения элемента W; е —-после включения элемента Z; ж — после исключения элемента X; з —- после исключения элемента Y.
Очередь, организованная описанным выше способом, имеет ограниченное применение, поскольку массив, состоящий из queueSize элементов, будет исчерпан, как только в очередь будет добавлен элемент, местоположение которого в массиве определится индексом, равным значению константы queueSize. Теперь в очередь не удастся добавить ни одного элемента, даже если большинство находящихся в ней элементов будет исключено. Необходимо каким-то образом использовать память, освобождающуюся по мере того, как элементы исключаются из очереди, чтобы переполнение очереди наступало тогда, когда действительно введено слишком много элементов. Этого нетрудно достичь, если представить массив в виде кольцевого бу-
фера, схема которого изображена на рис. 3.8. Такой буфер по достижении его конца начинает заполняться с начала. Для добавления и исключения элемента х, если очередь организована в виде кольцевого буфера, можно использовать следующие последовательности операторов:
{Добавление элемента х}-
queue [tail]: = х; tail: = tail+1;
IF tail> queueSize THEN tail := 1;
{Исключение элемента x}
x := queue'[head]; head := head+1;
IF head > queueSize THEN head := 1;
Очередь, как и стек, может переполниться, если мы попытаемся включить в нее слишком много элементов. Состояние
Рис. 3.8. Организация очереди в виде кольцевого буфера.
переполнения очереди может быть обнаружено, когда конец очереди «догонит» ее начало. При рассмотрении рис. 3.7 может создаться впечатление, что значение переменной tail всегда больше значения переменной head (или равно ему) и, следовательно, переполнение очереди можно обнаружить путем проверки условия taiKhead. Однако в связи с тем, что очередь имеет кольцевую организацию, условие taiKhead может оказаться истинным и в режиме нормального функционирования. Таким образом, следует выполнять проверку равенства tail + + l=head, прежде чем включать в очередь новый элемент. Эту
проверку можно производить с помощью следующего фрагмента программы:
{Проверка переполнения очереди и включение в нее элемента х}
temp := tail+1;
IF temp > queueSize THEN temp := 1;
IF temp=head THEN {Переполнение, сообщение об ошибке}
ELSE BEGIN queue [tail] := x; tail := temp END;
Если значение переменной tail на единицу меньше значения переменной head, нельзя добавлять элементы в очередь, несмотря на то что имеется одна свободная ячейка в памяти. Иначе критерием заполнения очереди было бы выполнение равенства head=tail, которое является истинным и для пустой очереди. Следовательно, в очереди может содержаться только queueSize — 1 значащих элементов. При этом удобно считать, что в очереди может находиться от 0 до queueSize—1 элементов и всего имеется queueSize соответствующих состояний очереди.
В надлежащим образом составленной программе перед исключением элемента из очереди должна выполняться проверка наличия в ней элементов. Такая проверка реализуется в следующем фрагменте программы:
{Проверка наличия элементов и исключение элемента х}"
IF head=tail THEN {Очередь пуста, сообщение об ошибке} ELSE BEGIN
х := queue[head]; head := head+1;
IF head>queueSize THEN head := 1;
END;
Существует также другой, отличный от рассмотренного способ организации очереди, при котором вместо того, чтобы при исключении элемента из очереди изменять значение указателя на начало очереди, все элементы очереди перемещаются на один шаг к ее началу. Такой прием редко используется в программах, поскольку он приводит к значительным дополнительным затратам времени на последовательное перемещение каждого элемента очереди. Однако этот способ организации очереди довольно широко применяется при ее аппаратной реализации, поскольку сдвиговый регистр может работать таким образом, чтобы все перемещения элементов выполнялись параллельно.
Пример программы ввода-вывода текста с использованием очереди представлен на рис. 3.9. Согласно этой программе, обработка литер ведется по принципу организации очереди. Всякий раз, когда вводится литера «!», из очереди изымается первое слово и выводится на печать. Здесь словом является последовательность литер без пробелов; признаком конца слова слу-
жит пробел. Таким образом, при выводе слов на печать сохраняется порядок поступления вводимых литер.
Структуры данных типа «очередь» широко используются в операционных системах больших ЭВМ. Их применяют для хра-
PROGRAM WordQueue (input,output);
{Последовательность вводимых слов организуется в очередь;
при этом каждое слово оканчивается пробелом, а в конце последовательности слов ставится точка. При поступлении литеры <’> одно слово извлекается из очереди и выводится на печать. Литера К?» в'очередь не ставится}
CONST qLen = 200; {queue, length} endChar = *.'; {input terminator} space = * *; {word separator} dumpChar = * 1 *; {command to print one word)
VAR inChar : char;
queue : ARRAY [1..qLen] OF char;
head, tail, temp : integer;
wordFlag : boolean;
BEGIN
head : = 1; tail := 1; read(inChar); {Init queue and get first char.} WHILE inChar <> endChar DO {Process a character at a time.}
BEGIN
IF inChar <> dumpChar THEN
BEGIN {Enqueue the character.}
temp := tail + 1;
IF temp. > qLen THEN temp := 1;
IF temp <> head THEN BEGIN queue[tail] := inChar; tail := temp END; END {Lose characters'on overflow.}
ELSE
BEGIN {Dump one toord to output.} wordFlag := true;
WHILE (headOtail) AND wordFlag DO
BEGIN
wordFlag := queue [head] Ospace;
write(queue[head]); head := head+1; {Dequeue, print char» IF head > qLen THEN head := 1;
END;
writein; {Start a new line after each printed word.}
END;
read(inChar); • {Get next char.} END;
END.
Рис. 3.9. Пример программы с использованием очереди.
нения запросов на обслуживание в порядке их поступления. По мере освобождения нужных ресурсов запросы обслуживаются по принципу «поступивший первым обслуживается первым». Кроме того, очереди являются наиболее естественными структурами данных в программном обеспечении, используемом для
7—1393
выполнения ввода-вывода данных в любых ЭВМ. Когда устройства и программы имеют различные временные характеристики, можно прибегнуть к очередям для временного хранения данных, поступающих от быстродействующего устройства или какой-либо программы. Позже эти данные без изменения порядка их поступления могут быть переданы по запросу другому устройству или программе. (Программы, в которых для организации ввода-вывода данных применяются очереди, рассматриваются в разд. 11.6.) '
В рассмотренных программах на языке Паскаль для реализации очередей использовались массивы, а переменные head и tail играли в них роль индексов. В программах на языке ассемблера можно избежать вычисления индексов массива, используя в качестве значений переменных head и tail абсолютные адреса памяти. (Набор программ с использованием очередей, написанных на языке ассемблера, будет представлен в разд. 9.3.)
ОДНОНАПРАВЛЕННЫЕ СВЯЗАННЫЕ СПИСКИ
В стеки или очереди данные можно добавлять в какой-либо один конец структуры данных (это относится и к извлечению данных); проведение этих операций с данными, расположенными в произвольном месте структуры, не допускается. Связанный список является одномерной структурой данных, в произвольно выбранное место которой могут включаться данные, а также изыматься оттуда. Чтобы обеспечить такую гибкость, в каждый элемент добавляется звено связи, указывающее следующий элемент списка.
На рис. 3.10 дано схематическое изображение однонаправленного связанного списка (двунаправленные связанные списки будут рассмотрены в следующем разделе). Переменная с именем head использована здесь как указатель первого элемента списка. Все элементы списка имеют одинаковый формат: звено связи используется для указания следующего элемента, а данные могут быть представлены в произвольном формате. Разме-; щение звена связи и блока данных одного элемента в смежных участках памяти ЭВМ не является обязательным. Элемент, следующий в списке за элементом X, называется преемником элемента X; элемент, расположенный перед элементом X, называется предшественником элемента X. Если элемент X не имеет преемника, то содержащемуся в нем указателю присваивается значение null.
Чтобы вставить элемент Q в список после элемента Р, необходимо сделать так, чтобы звено связи элемента Q определяло бывший преемник элемента Р, а звено связи элемента Р указывало элемент Q. Для того чтобы исключить из списка элемент Y, стоящий после элемента X, нужно указатель, содер-
жащийся в элементе X, изменить таким образом, чтобы он задавал преемник элемента Y.
Для определения связанных списков и других динамических структур данных в языке Паскаль предусмотрено эффективное
Указатель (звено связи)
ДанныеДанныеб head: \ни66 ^б head: \ни66 ^а — формат элемента списка; б — пустой список; в — список из четырех элементов; г — включение элемента Q после элемента Р; д — исключение элемента Y.а — формат элемента списка; б — пустой список; в — список из четырех элементов; г — включение элемента Q после элемента Р; д — исключение элемента Y.
Данные
Данные
б head: \ни66 ^
б head: \ни66 ^
а — формат элемента списка; б — пустой список; в — список из четырех элементов; г — включение элемента Q после элемента Р; д — исключение элемента Y.
а — формат элемента списка; б — пустой список; в — список из четырех элементов; г — включение элемента Q после элемента Р; д — исключение элемента Y.
средство, основанное на типах данных pointer (указатель) и record (запись). Рассмотрим, как организуются связанные списки с использованием массивов, с которыми мы уже знакомы.
В качестве примера обсудим определение списка группы, содержащего не более 100 фамилий студентов и их экзаменационные оценки. Можно выделить память для массива 100X20 литерного типа, предназначенного для размещения 100 фамилий по 20 литер в каждой, память для массива оценок (вещественного типа), а также память для массива указателей, значения-
ми которых являются целые числа. Указанные массивы можно описать следующим образом:
VAR name : ARRAY [1..100, 1..20] OF char;
score : ARRAY [1..100] OF real;
link : ARRAY [0..100] OF integer;
Каждая 20-литерная строка массива name и соответствующий ей компонент массива оценок score составляют блок данных одного элемента списка; компонент массива link, имеющий такой же порядковый номер, как и номера компонента массива score и строки массива паше, относящиеся к некоторому элементу, является звеном связи этого элемента. В качестве значения звена связи некоторого элемента может быть использовано допустимое значение индекса рассматриваемых массивов или значение null. В нашем примере положим null =—1; все прочие значения звеньев связи элементов списка должны лежать в диапазоне 1... 100. Массив link имеет дополнительный компонент Нпк[0]. Этот компонент служит указателем начала списка. В качестве указателя начала списка могла бы использоваться и отдельно объявленная переменная целочисленного типа, однако, как нам станет ясно впоследствии, некоторые операции над списками выполнять легче, когда доступ к началу описка осуществляется так же, как и к другим его элементам.
Предположим, что в рассматриваемом примере q является индексом элемента, который должен быть вставлен в список после элемента с индексом р, что блок данных уже заполнен, тогда включение элемента можно выполнить с помощью следующей последовательности операторов:
{Включение элемента Q после элемента Р}
link[q] := link[p];
link[p] : = q;
Исключить элемент из списка еще проще; следующий оператор предназначен для исключения преемника элемента с индексом х — элемента Y:
{Исключение преемника элемента X}
IF link[x]<>null THEN link[x]; = [link[x]]
ELSE {Ошибка, элемент X не имеет преемника};
На рис. 3.11 приведена программа, которая предназначена для ввода фамилий и экзаменационных оценок студентов и включения их в список группы таким образом, чтобы сохранить упорядоченность фамилий в соответствии с алфавитом. В начале программы предусмотрено присваивание началу списка (link [0]) значения null; массивы паше и score в начале выполнения программы не содержат данных. Затем осуществляется
PROGRAM ClassList (input,output) {Осуществляется ввод последовательности Фамилий и экзаме национных оценок, разделенных пробелами; признаком конца последовательности служит точка. Каждая пара Фамилия-оценка помещается в связанный список, где фамилии распслагаются в алфавитном порядке. По окончании ввода исходных данных Фамилии студентов, средние экзаменационные оценки которых оказались ниже средней оценки по группе, удаляются из списка, а Фамилии и оценки остальных студентов выводятся на печать}-
CONST'1
listLen = 100; nameLen = 20; (List and .name lengths} null = -1; {End-of-list link value}*
endChar = '.'; space = ' 1; {Terminator and separator} "
VAR name : ARRAY [1..listLen, 1..nameLen] OF char; score: ARRAY [1..listLen] OF real; link : ARRAY [0..listLen] OF integer; {link[0] = head} sum, avg : real;/
nNames, nChar, j, prev, next : integer; inChar : char; greater : boolean;
BEGIN sum:=0; nNames:=0; link [0]:=null; {Initialization} read (inChar); {Get first phar.} WHILE inChar <> endChar DO
BEGIN {Read all names and scores.} pNames : = nNames + 1; nChar := 1; WHILE (inCharOspace) AND (inCharOendChar) DO BEGIN {Read one name.} * IF nChar <= nameLen THEN name [nNames,nChar] := inChar;-
read (inChar) ; nChar := nChar + 1;
END;
FOR j:=nChar TO nameLen DO {Pad end with spaces} name[nNames,j] := space;,
readln(score[nNames]); {Get score and start new line} sum := sum + score[nNames]; {Add to sum for average.} prev := 0; next := link[0]; {Point to class list head.} greater := false;.
WHILE (next <> null) AND (NOT greater) DO.
BEGIN {Chase down list until end or greater name found.} FOR j := nameLen DOWNTO 1 DO greater := (name[next,j] > name[nNames,j])
OR ((name [next, j] = namelnNames, j]) AND greater);
IF NOT greater THEN {Get next item}-
BEGIN prev := next; next := link[next] END;-
END; {Then insert new item after prev.}
link[nNames] :- next; link[prev] := nNames; IF nNames < listLen THEN read (inChar)
ELSE inChar := endChar; {Terminate if too many names,} END;
avg := sum/nNames; {Compute average.)
writein('Average score:', avg, 'Lower scores will be purged1)? prev := 0; {Purge all scores lower than class average.) ’ WHILE link[prev] <> null DO
BEGIN
IF score[link[prev]] < avg THEN
•* link[prev] :=> link [link [prev] J
ELSE prev := link[prev];
END;,
next := link[0]; writein; {Now print the remaining sorted list.} WHILE next <> null DO
BEGIN
FOR j := 1 TO nameLen DO write(name[next,j]) ;
•writein(score[next]); next ;= link[n'ext];
END;
END*
Рис. 3.11. Программа сортировки фамилий и экзаменационных оценок студентов.
ввод очередной пары значений, т. е. фамилии и соответствующей оценки, и их размещение в следующей свободной строке массива name и в следующей ячейке массива score соответственно. Далее с самого начала списка производится его просмотр до тех пор, пока не будет найдена фамилия, которая не расположена по алфавиту, или не будет достигнут конец списка. Затем новая фамилия включается в список таким образом, чтобы сохранялась упорядоченность фамилий в списке по алфавиту. После того как все исходные данные введены, из списка исключаются фамилии тех студентов, экзаменационные оценки которых оказались ниже средней оценки по группе. Фамилии оставшихся в списке студентов и их оценки выводятся на печать.
Отметим, что применение в качестве начала списка нулевого элемента массива link позволяет производить операции включения элементов в начало списка и исключения их оттуда без выполнения специальных проверок. Использование отдельной переменной head привело бы к усложнению выполнения этих операций.,
В нашем примере для хранения данных и связей в списке применялись массивы, а индексы при этом играли роль указателей/ Использование индекса массива в качестве указателя приводит к появлению в объектной программе команд, предназначенных для вычисления адреса памяти для соответствующего компонента массива при каждой ссылке на элемент списка. В программах на языке ассемблера можно избежать дополнительного увеличения объема программы, если использовать в качестве указателей абсолютные адреса памяти; соответствующие примеры будут представлены в разд. 7.3.
ДВУНАПРАВЛЕННЫЕ СВЯЗАННЫЕ спцски
Описанные нами операции включения элемента в однонаправленный список и исключения его оттуда оказывают воздействие на преемника некоторого элемента списка. Действительно, операция включения состоит в размещении элемента Q после элемента Р, а операция исключения предполагает удаление из списка преемника элемента X. Допустим, что указатель установлен на элемент X некоторого списка и необходимо выполнить, например, такие операции: «Вставить элемент Y перед элементом X», «Удалить элемент X». В обеих операциях должно изменяться значение указателя, содержащегося в предшественнике элемента X. Если в нашем распоряжении имеется только указатель на элемент X, то единственный способ отыскания предшественника элемента X заключается в возвращении,к началу списка и проведении его просмотра вплоть до обнаружения элемента X. Включение элемента Y перед элементом X может быть выполнено с помощью следующего фрагмента программы:_
{Включение элемента Y перед элементом X} *
{Полагаем, что link [0] является началом списка}
prev := 0;
WHILE (link [prev] О null) AND (link [prev] < >x) DO prev := link[prev];-
IF link [prev] =x THEN
BEGIN link[prev] := y; link[y] := x END
ELSE {Ошибка, элемент X не найден};
Для исключения элемента X из списка потребовалось бы выполнить, например, следующие операторы:
{Исключение элемента X}
*prev := 0;
WHILE (link [prev] О null) AND (link[prev] < >x) DO prev := link[prev];
IF link [prev] =x THtN link [prev] := link [link [prev]];
ELSE {Ошибка, элемент X не найден};
Операция исключения последнего элемента из однонаправленного списка всегда связана с просмотром всего списка. Иногда для избежания затрат времени на просмотр списка имеет смысл воспользоваться двунаправленным связанным списком.
В двунаправленном связанном списке каждый элемент имеет два указателя: один succlink— указывает на преемника данного элемента, другой — predlink— на предшественника. Теперь предшественник или преемник некоторого элемента дости-
Рис. 3.12. Двунаправленные связанные списки:
а — формат элемента списка; б — пустой список; в — список из четырех элементов; г—* включение элемента Y перед элементом X; д — исключение элемента X.
гается за один шаг, однако каждый раз при включении или удалении элемента из списка требуется изменить больше связей, или значений, указателей. При использовании двунаправленного списка рассматриваемые нами операции можно выполнить с помощью следующих последовательностей операторов:
{Включение элемента У перед элементом X}
succlink[y] :=х;
predlink[y] := predlink[x];
succlink [predlink [х] ] := у;
predlink[x] := у;
{Включение элемента Y после элемента X}
succlink [у] := succlink [х];
predlink [у] := х;
predlink [succlink [х]] := у;
succlink [х] := у;
{Исключение элемента X}
predlink [succlink [х]] := predlink [х];
succlink [predlink [x]] := succlink [x];
Рис. 3.12 иллюстрирует выполнение рассматриваемых операций. Как и в случае однонаправленного списка, операции включения и исключения выполнять легче, если к началу (head) и концу (tail) списка возможен такой же доступ, как и к другим элементам списка. Этого можно достичь, если положить head = = succlink [0] и tail = predlink[O]. Непосредственно после начальной установки начало и конец списка указывают друг на друга.
УПРАВЛЕНИЕ ПАМЯТЬЮ
ПРИ РАБОТЕ СО СПИСКАМИ
До настоящего момента мы предполагали, что всегда имеется достаточный объем памяти для размещения новых элементов списка, и не испытывалй беспокойства относительно «потери» памяти, происходящей при исключении некоторого элемента из списка. Однако в большинстве приложений, в которых используются списки, нужно управлять имеющейся в распоряжении памятью, чтобы не допускать нерационального расходования памяти.
Простейший способ решения этой задачи состоит в организации списка «свободных» (неиспользуемых) ^областей памяти. В начале работы программы все незанятые области памяти помещаются в список свободных областей. Когда возникает необходимость добавить новый элемент в некоторый список, из списка свободных областей извлекается первая область, которая и используется для размещения нового элемента. При исключении элемента из некоторого списка освободившаяся область памяти помещается в список свободных областей. В примере программы, йриведенном в разд. 3.5, фигурировал список группы*. Используя этот список, можно после описания необходимых переменных образовать список свободных областей:
VAR
name: ARRAY [1..100, 1..20] QE-char;
score : ARRAY [1..100] OF real;
link : ARRAY [0..100] OF integer;
free, next, new, temp : integer;
BEGIN
link[0] := null; {Установка начала списка группы}
free := 1; {Начальная установка списка свободных областей}
FOR next := 1 ТО 99 DO
link [next] : = next+1;
link [100] := null;
Чтобы включить новый элемент после элемента X списка группы, используем первую область списка свободных областей:
{Включение нового элемента после элемента X}
IF freeonull THEN
BEGIN
new:= free; {Получение первой области из списка свободных областей}
fгее : = link[fгее]; {Исключение области из списка свободных областей}
link [new] := link[x]; link[x] := new; {Включение} END;
ELSE {Ошибка, свободных областей нет} ;
После исключения преемника элемента X освободившаяся область помещается в список свободных областей:
{Исключение преемника элемента X}
IF link [х] <> null THEN
BEGIN
temp := link[x]; {Сохранение значения указателя следующего элемента}
link[x] := link [link [x]]; {Исключение элемента}
link [temp] := free; {Включение области в начало списка свободных областей}.•
free := temp;
END; ,
ELSE {Ошибка, элемент X не имеет преемника};
Вместо систематического возвращения областей, исключенных из списков элементов, в список свободных областей памяти можно воспользоваться методом, носящим название «сбор мусора». Всякий раз, когда список свободных областей становится пустым, производится проверка всех активных списков. Любой элемент, который не входит ни в один активный список, считается ненужным, и соответствующая область возвращается в список свободных областей. Это исключает дополнительные расходы времени на изменение списка свободных областей при каждом исключении элемента из какого-либо активного списка; периодическое выполнение операции «сбора мусора» может оказаться довольно эффективным.
Операция «сбора мусора» обычно используется в приложениях, связанных с обработкой сложных списков, когда в нескольких списках содержатся одинаковые элементы или даже группы элементов. В таких случаях при исключении элемента из одного списка неизвестно, имеются ли ссылки из других списков на тот же самый элемент, и, следовательно, соответствующую область нельзя включать в список свободных областей. Организация списка свободных областей и «сбор мусора» используются во время функционирования программных систем обработки списков, основанных на языках обработки списковых структур, подобных языку LISP. Все действия по управлению памятью выполняются программным путем.
Известен интересный пример использования списков в качестве очередей. Рассмотрим ЭВМ, которая распределяет сообщения по 100 различным местам назначения в сети. Предположим, что иногда сообщения генерируются столь интенсивно, что невозможно их передать без задержки в определенный пункт назначения. Тогда для временного хранения сообщений следует организовать очереди, соответствующие пунктам назначения. Допустим, что для наихудшего случая очередь составляет 20 сообщений для определенного пункта назначения. Тогда, предусмотрев отдельную очередь максимальной длины для каждого пункта назначения и используя для очередей структуры данных, рассмотренные в разд. 3.4, необходимо выделить в памяти пространство для 100-20=2000 сообщений. Если длина каждого сообщения составляет 100 литер, то следует предусмотреть пространство памяти для хранения 200 000 литер.
Предположим, нам также известно, что в системе не будет скапливаться более 100 сообщений, даже если для некоторых пунктов назначения сформировано по 20 сообщений. Поэтому можно распределить память для списка свободных областей, содержащего 100 областей и организовать каждую очередь в виде связанного списка, использующего список свободных областей. Во время нормального функционирования системы большинство очередей бывают пустыми или короткими, однако должно быть зарезервировано достаточное количество свободных областей памяти на тот случай, когда длина некоторого количества очередей существенно увеличивается. Благодаря использованию одного списка свободных областей, можно обеспечить функционирование системы, располагая пространством памяти только на 10 000 литер.
УПРАЖНЕНИЯ
Напишите программу на языке Паскаль, согласно которой вводится последовательность литер и организуется массив. Определите следующие три константы литерного типа: backspace, endofline и endoffile. При получении
управляющей литеры backspace («Возврат на шаг») предыдущая литера, записанная в массив, исключается из него. Когда вводится управляющая литера endofline («Конец строки»), находящиеся в массиве литеры должны быть напечатаны. Следующие вводимые литеры должны записываться в массив, начиная с первого элемента. После получения управляющей литеры endqffile («Конец файла») должна быть напечатана последовательность литер, содержащаяся в массиве, и, кроме того, выполнение программы должно быть остановлено.
Напишите программу на языке Паскаль, которая обеспечивала бы ввод последовательности целых чисел и размещение их в памяти, выделенной для организации массива. Далее числа в массиве должны быть упорядочены следующим образом: вначале среди чисел массива отыскивается наименьшее и производится обмен местами этого числа с первым числом массива; затем отыскивается наименьшее число среди всех чисел массива, начиная со второго числа, и производится обмен местами найденного числа со вторым числом массива; аналогично должны бызь обработаны все числа массива. После этого следует вывести на печать упорядоченную последовательность чисел. (Указание: после каждого цикла поиска наименьшего числа нужно вести поиск только среди оставшихся неупорядоченных чисел массива, т. е. перед началом поиска продвинуться по массиву на один шаг. Следует отметить, что рассмотренный алгоритм не является достаточно эффективным алгоритмом упорядочения.)
Напишите программу на языке Паскаль, предназначенную для определения простых чисел, заключенных в диапазоне от 2 до 1000, используя булев массив prime[2.. 1000]. Вначале присвойте всем элементам массива значение «истинно». Начиная с элемента массива с индексом, равным 2-2, присвойте всем элементам массива, индексы которых кратны 2, значение «ложно». Продолжите присвоение значения «ложно» элементам массива с индексами, кратными 3, 4, 5 и т. д., до тех пор, пока не будут обработаны и помечены как ложные все элементы массива с непростыми номерами. Напечатайте все простые числа из заданного диапазона.
Оптимизируйте предыдущую программу, отмечая только те элементы массива, номера которых кратны уже определенным простым числам. При этом не потребуется помечать элементы массива, индексы которых кратны 4, 6, 8, 9 и т. д., так как им уже было присвоено значение «ложно» на предыдущих шагах.
Дана матрица А, состоящая из mXr-элементов, и матрица В, содержащая rXn-элементов. Произведением матриц А и В является матрица С (тХп)» элемент С,, которой определяется следующим образом:
Cij = 2 Aik*BM
l<k<r
Напишите на языке Паскаль программу, с помощью которой выполняется ввод матриц А и В, вычисление их произведения и вывод на печать. Предположите, что максимальное значение величин т, п и г равно 20, ввод фактических значений указанных величин должен осуществляться в начале программы.
В программах, представленных на рис. 3.1 и 3.2, предусмотрен ввод числа студентов в группе. Это чисХо должно содержаться в файле входных данных,-что может привести к ошибкам при его модификации. Предложите способ изменения формата входных данных и программы, при которых не потребуется подсчитывать и осуществлять ввод числа студентов.
Измените программу на языке Паскаль, представленную на рис. 3.2, исключив из нее первый и последний циклы, реализованные с помощью оператора FOR.
Напишите на языке Паскаль: объявления; операторы, выполняющие установку начальных значений; последовательность операторов, предназначен-
ную для проверки стека на переполнение"и загрузки в него нового элемента; последовательность операторов, используемую для проверки наличия в стеке элементов и извлечения элемента из стека. Примите, что при этом указатель стека sp установлен на верхний элемент стека.
Напишите на языке Паскаль: объявления; операторы, выполняющие установку начальных значений; последовательность операторов, предназначенную для проверки стека на переполнение и загрузки в него нового элемента; последовательность операторов, предназначенную для проверки наличия в стеке элементов и извлечения элемента из стека. Предположите, что при заполнении стека значение указателя стека уменьшается, а не увеличивается.
Измените программу на Паскале, представленную на рис. 3.5, так, чтобы для обеспечения завершения второго и третьего циклов, реализованных с помощью оператора WHILE, не требовалось записывать в стеки в качестве первых элементов константы minNum—1 и maxNum+1. Как такое изменение повлияет на время выполнения рассматриваемых циклов?
Измените программу на Паскале, представленную на рис. 3.5, таким образом, чтобы оба стека размещались в одном массиве. Началом одного стека является первый элемент массива, значение указателя стека при заполнении последнего возрастает, началом другого стека служит последний элемент массива, значение указателя стека при заполнении стека уменьшается. Укажите, какой величины потребуется массив при таком размещении стеков для сортировки последовательности из 200 чисел?
Считая, что в языке высокого уровця не предусмотрено выполнение проверки нарушения границ изменения индексов массива, используемого для организации стека, предложите метод обнаружения, переполнения стека,, который позволяет обнаружить переполнение стека уже после его наступления без проведения проверок перед каждым выполнением операции загрузки элемента в стек. Укажите допущения, принятие и соблюдение которых обеспечат эффективность метода.
Напишите на языке Паскаль: объявления; операторы, выполняющие установку начальных значений; последовательность операторов, предназначенную для проверки очереди на переполнение и добавления в нее нового элемента; последовательность операторов, служащую для проверки наличия в очереди элементов и исключения элемента из очереди. В очереди не может находиться более queueSize элементов, где queueSize— размер массива, в котором организуется очередь. (Указание: воспользуйтесь дополнительной переменной для установления состояний очереди — «очередь полна», «очередь пуста» — в случае, когда head=tail.)
Напишите на языке Паскаль: объявления; операторы, выполняющие установку начальных значений; последовательность операторов, предназначенную для проверки очереди на переполнение и добавления в нее нового элемента; последовательность операторов, служащую для проверки наличия в очереди элементов и исключения элемента из очереди. Очередь организуйте так, чтобы ее начало находилось в нижней части буфера памяти, а элементы очереди сдвигались бы вниз при каждом исключении из нее элемента. Какое количество элементов может содержать очередь при использовании такого метода ее организации в буфере, размер которого равен queueSize?
Объясните, каким образом можно исключить переменную wordFlag, описанную в программе, представленной на рис. 3.9, и использовать для выхода из внутреннего цикла программы оператор GOTO. Целесообразно ли применение в данном случае оператора GOTO? Рассмотрите возможность добавления к операторам языка Паскаль оператора EXIT, который в ситуациях, подобных рассматриваемой нами, обеспечивает выход из цикла. Использование оператора EXIT для выхода из цикла в большей степени отвечает требованиям структурного программирования по сравнению с применением других средств.
Измените программу, представленную .на рис. 3.9, таким образом, чтобы для разделения слов можно было использовать несколько пробелов. Резуль-
таты выполнения данной программы не должны зависеть от числа используемых пробелов.
Спросите ваших коллег, обладающих опытом работы в области вычислительной техники, каким образом они организуют совокупность з^дач, подлежащих решению, — в виде стека или в виде очереди. Обратите внимание на необычные и «привлекательные» структуры данных.
Очередь с двумя концами позволяет выполнять операции по включению и исключению элементов на обоих концах. Для организации такой очереди требуются блок памяти и переменные head и tail, используемые для обычной очереди. В этом случае имеется возможность выполнять следующие операции: а) добавлять элемент X в начало очереди; б) добавлять элемент X в конец очереди; в) исключать элемент X из начала очереди; г) исключать элемент X из конца очереди.
Напишите для этих функций последовательности операторов, включите в эти последовательности операторы для проверки аномальных ситуаций. Заметьте, что операции пп. «б» и «в» совпадают с операциями для обычной очереди, в то время как операции пп. «а» и «в» или «б» и «г» выполняются аналогично операциям над стеком.
Почему при рассмотрении в разд. 3.6 примера связанного списка вместо переменной pred для ссылки на предшественника использовалась переменная prev?
Напишите на языке Паскаль программу для изменения порядка расположения элементов в однонаправленном связанном списке на противоположный, используя при этом промежуточную память столь малого объема, сколь это возможно.
Выполните предыдущее упражнение, применяя для хранения указателей стек.
Чтобы эффективно присоединять элементы к концу однонаправленного списка, используйте при его организации указатель tail. Напишите последовательности операторов, обеспечивающие выполнение каждой из следующих функций: а) добавление элемента q в конец списка; б) включение элемента q после элемента р; в) исключение преемника элемента х. Обратите внимание на правильность изменения значений как указателя tail, так и других указателей.
Анализируя последовательности операторов, предназначенные для выполнения включения элементов в двунаправленный связанный список и для исключения из него элементов, убедитесь в правильности их действия в следующих специальных случаях: а) включение элемента в пустой список по указателю head; б) включение элемента в пустой список по указателю tail; в) исключение элемента из списка, в котором содержится только один элемент.
Напишите на языке Паскаль программу, предназначенную для изменения порядка расположения элементов двунаправленного связанного списка на противоположный. -—-
Объясните, почему в программе, представленной на рис. 3.11, константе null может быть присвоено значение 0, даже если в качестве начала однонаправленного связанного списка используется элемент массива с индексом 0. Объясните также, почему константа null не может иметь значение 0 в программе, реализующей двунаправленный связанный список, которая представлена в разд. 3.7.
Элементы двунаправленного связанного списка могут быть найдены при просмотре списка в том или другом направлении, т. е. нам всегда доступен как предшественник, так и преемник текущего элемента списка. В таком случае вместо хранения в каждом элементе указателя на предшественника и указателя на преемника можно хранить значение функции двух указателей. Тогда, располагая значением одного указателя, можно, воспользовавшись функцией, получить значение другого указателя. Опишите две функции, об-
ладающие данным свойством, для хранения значений которых требуется не' больше памяти, чем для хранения одного указателя.
Добавьте список свободных областей*в программу, представленную нш рис. 3.11. Для получения области из списка свободных областей и для вклЮ' чения области, соответствующей отработанному элементу, в этот список определите процедуры GetFree и PutFree. Предусмотрите проверку наличия элементов в списке свободных областей.
Опишите потенциальные преимущества использования элемента массива link, например элемента link[—1], для представления начала списка свободных областей вместо отдельной переменной.
ЛИТЕРАТУРА
В настоящее время лучшим пособием по структурам данных является книга Кнута (Knuth D., Fundamental Algorithms, Addison-Wesley, 2nd ed., 1973 [Имеется перевод: Кнут Д. Искусство программирования для ЭВМ. Т. 1. Основные алгоритмы. — М.: Мир, 1976.]). Содержание данной книги ориентировано прежде всего на программистов, использующих для написания программ язык ассемблера; приведенные здесь примеры даны на языке ассемблера некоторой универсальной гипотетической машины MIX. В другой книге того же автора (Knuth D., Sorting and Searching, Addison-Wesley, 1973 [Имеется перевод: Кнут Д. Искусство программирования для ЭВМ. Т. 3. Сортировка и поиск. — М.: Мир, 1978.]) представлен обширный материал по структурам и их использованию в программах сортйровки и поиска.
Вирт в своей книге, на которую мы ссылались выше (Wirth М, Algorithms+ +Data Structures=Programs, Prentice-Hall, 1976) вначале описывает основные структуры данных, используемые в программах, наЬисанных на языке Паскаль, а затем рассматривает некоторые широко применяемые алгоритмы и структуры данных. Структуры данных обсуждаются в большинстве книг по основам программирования, среди них можно отметить следующие: Stone Н. S., Siewiorek D. Р., Introduction to Computer Organization and Data Structures, McGraw-Hill, 1975; Eckhouse, Morris, Minicomputer Systems, Prentice-Hall, 2nd ed., 1979; Gear C. W., Introduction to Computer Science, SRA, 1973.
44
4
4
СИСТЕМЫ СЧИСЛЕНИЯ И АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ
Изучение систем счисления и арифметических операций, используемых в ЭВМ, очень важно для понимания того, как про- :изводится обработка числовых данных в вычислительных машинах. Знание систем счисления, как и математики вообще, может пригодиться и в повседневной жизни. Вот несколько примеров из моего опыта.
При праздновании 31-летия моей жены мне удалось украсить торт всего пятью свечами.
Я сумел сделать остаток моего лицевого счета большой положительной величиной, тогда как в действительности он был отрицательным.
После всех моих неудачных попыток отыскать НРС в телефонном справочнике по алфавиту я все-таки нашел эту аббревиатуру расположенной в казавшемся на первый взгляд неподходящем месте.
В первом случае не испортить праздник мне помогла двоичная система счисления. Во втором случае я воспользовался системой представления чисел дополнением по десяти. А что касается последнего случая, то, вспомнив, как с помощью чисел представляются символы в ЭВМ, я понял, почему между Heywood и Hiatt отсутствует аббревиатура НРС. После изучения данной главы вы также сможете использовать различные системы счисления в ваших повседневных делах, кроме того, вы получите представление об арифметике, используемой в вычислительных машинах.
ПОЗИЦИОННЫЕ СИСТЕМЫ СЧИСЛЕНИЯ
Во всех вычислительных машинах используются позиционные системы счисления. В таких системах число представляется в виде последовательности цифр, позиции каждой из которых присвоен определенный вес. Например, величину D 4-разрядно- го десятичного числа d3d2dido можно получить следующим образом:
D = d3- 1034-d2.103 Ч-di -101 H-d0 • 10°
Каждая.цифра di имеет вес, равный 10\ Поэтому, например, величина числа 6851 может быть представлена так:
6851 = 6.1000+8.100+5.10+1-1
Для того чтобы иметь возможность оперировать как положительными, так и отрицательными степенями числа 10, в десятичной системе счисления используют десятичную точку. Таким образом, число did0.^i^-2 имеет значение
D = dv Ю^^. 10°+^. lO^ + d^. IQ-2
Например, значение числа 34.85 вычисляется так:
34.85 = 3-10 + 4.1+8.0.1 +5-0.01
В любой позиционной системе счисления позиция каждой цифры di имеет вес bi, где b есть основание системы счисления. Общая форма записи чисел в таких системах счисления выглядит следующим образом:
^р-1^р-2" * * ^о.сС1^-2* * * ^-п
где р— число цифр, расположенных слева, а п— число цифр, расположенных справа от точки, отделяющей целую часть от дробной части числа. Значение числа представляет собой сумму произведений отдельных цифр на основание системы счисления в соответствующей степени*.
о= 2 4-»-p—\>i>—n
Крайний слева бит двоичного числа называется битом старшего разряда (MSB), а крайний справа — битом младшего разряда (LSB).
ВОСЬМЕРИЧНЫЕ
И ШЕСТНАДЦАТЕРИЧНЫЕ ЧИСЛА
Для пользователей ЭВМ кроме систем счисления с основанием 2 и 10 важны также и системы с другими основаниями. В частности, удобное представление чисел в ЭВМ обеспечивают восьмеричная и шестнадцатеричная системы счисления, в которых используются основания 8 и 16 соответственно.
В табл. 4.1 приведены двоичные числа от 0 до 1111 и их восьмеричные, десятичные и шестнадцатеричные эквиваленты. Для записи чисел в восьмеричной системе счисления требуется 8 цифр, и поэтому используются цифры от 0 до 7 десятичной системы. Для шестнадцатеричной системы требуется 16 цифр, так что наряду с десятичными цифрами от 0 до 9 здесь используют латинские буквы А, В, С, D, Е, F.
Таблица 4.1. Двоичные, десятичные, восьмеричные и шестнадцатеричные числа
Двоичные Десятич- Восьмерич- ^^^ь
Шестнадцатеричные числа
Последа ~ бат-сть.
из вбит
числа
ные числа ные числа
из 3 бит
0
00
000
0
0000
1
11
001
1
0001
10
22
010
2
0010
11
33
011
3
ООН
100
44
100
4
0100
101
55
101
5
0101
по
66
по
6
оно
111
77
111
7
0111
1000
810
—
8
1000
1001
911
—
9
1001
1010
1012
—
А
1010
1011
1113
—
В
1011
1100
1214
—
С
1100
1101
1315
—
D
1101
1110
1416
—
Е
1110
1111
1517
—
F
1111
Восьмеричные и шестнадцатеричные системы счисления удобны для представления двоичных чисел, так как их основания представляют собой степени числа 2. Поскольку последовательность из 3 бит (двоичных цифр) имеет восемь комбина-
ций, то каждое такое сочетание двоичных цифр можно однозначно представить с помощью одной восьмеричной цифры (третья и четвертая колонки табл. 4.1). Аналогично строка из 4 бит (двоичных цифр) может быть представлена одной шестнадцатеричной цифрой (пятая и шестая колонки той же таблицы) .
Таким образом, двоичные целые числа легко преобразовать в восьмеричные (или шестнадцатеричные). Для этого двоичное число, начиная от двоичной точки и двигаясь влево, делится на группы из 3 (или 4) бит и каждая группа заменяется соответствующей восьмеричной (или шестнадцатеричной) цифрой.
Приведем два примера:
1010110001102 = 101 011 000 1102=53068
1010110001102= 1010 1100 01102 = АС616
110110011101010012= 011 ОН 001 ПО 101 0012 = 3316518
110110011101010012 = 0001 1011 ООН 1010 10012= 1ВЗА91в
Из этих примеров следует, что с целью получения общего числа битов в строке, кратного трем или четырем, к двоичному числу можно добавить слева необходимое число нулей.
Если в двоичном числе содержатся цифры справа от двоичной точки, то они также могут быть преобразованы. Для этого, как и в предыдущем случае, начиная от двоичной точки, но двигаясь вправо, производится формирование групп по три бита в каждой. Если возникает необходимость, то к числу добавляется (теперь уже справа) соответствующее число нулей, как показано в примере, приведенном ниже:
11.10100110112=011.101 001 101 1002 = 3.51548
11.10100110112 = 0011.1010 ОНО 11002=З.А6С16
Преобразования чисел из восьмеричной и шестнадцатеричной систем счисления в двоичную также выполняются достаточно легко. Для этого каждую восьмеричную или шестнадцатеричную цифру необходимо заменить соответствующим двоичным эквивалентом из 3 или 4 бит.
Из приведенных ниже примеров следует, что предварительное преобразование в двоичную систему позволяет также преобразовывать числа из восьмеричной системы счисления в шестнадцатеричную и наоборот:
15738 = 001 101 111 0112=0011 0111 1011а=37В1в
А74816= 1010 0111 0100 10002=001 010 011 101 001 0002=
= 1235108
3.1458 = 011.001 100 101? = 0011.0011 0010 10002= 3.32816 8*
В программном обеспечении большинства ЭВМ двоичные числа представляют в виде восьмеричных и шестнадцатеричных чисел. Так, в мини-ЭВМ PDP-11 используются восьмеричные числа, во всех же остальных описанных в данной книге ЭВМ — шестнадцатеричные. Шестнадцатиразрядное двоичное число может принимать 65 536 значений, которым в восьмеричной системе счисления соответствуют числа в пределах от 0 до 177777, а в шестнадцатеричной — от 0 до FFFF.
Выбор восьмеричного или шестнадцатеричного представления двоичных чисел почти полностью определяется требованиями, предъявляемыми к документации. В отношении аппаратных средств использование той или иной системы оказывает влияние только на то, будут ли лампочки и переключатели, размещенные на передней панели ЭВМ, сгруппированы по три или по четыре..
ОСНОВНЫЕ СПОСОБЫ
ПРЕОБРАЗОВАНИЯ ЧИСЕЛ
В ПОЗИЦИОННЫХ СИСТЕМАХ СЧИСЛЕНИЯ
В общем случае преобразование числа из одной системы счисления в другую нельзя произвести путем простой замены. Для этого необходимо выполнить ряд арифметических операций. В данном разделе будет показано, как преобразуются числа из системы с любым основанием в числа системы с основанием 10 и обратно при помощи десятичной арифметики.
В разд. 4.1 мы выяснили, что значение числа в любой системе счисления определяется по формуле'
D- 2
р—\>1>—п
где b— основание системы, р— число цифр, расположенных слева, а п — число цифр, расположенных справа от точки. Таким образом, значение числа может быть найдено путем преобразования каждой его цифры в десятичный эквивалент и выполнения действий десятичной арифметики в соответствии с формулой. Некоторые примеры таких операций приведены ниже:
1ВЕ81в = 1 • 1634-11 -162 + 14-164-8 -16° = 714410
F1AC16= 15-1634-1-162+10-1614-12-16° = 6186810
437.58 = 4 - 82+3 - 81 + 7 • 8° 4 5 - 8"1 = 287.62510
122.13=Ь32+2.31+2-30+1.3-1 = 17.310
Наличие черты над цифрой 3 в последнем примере указывает на то, что эта цифра находится в периоде.
Общая формула преобразования целых чисел в десятичные имеет вид
D=^.-^p-iYbA-dp_^.b-V...>b-Vd1>b^-d0
В соответствии с этой формулой сначала производится сложение цифры старшего разряда с нулем и умножение суммы на Ь. Затем к полученному результату прибавляется следующая цифра, а сумма вновь умножается на b и так далее до тех пор, пока не будут использованы все цифры преобразуемого числа. Например, можно записать:
Р1АС16 = (((15).16 + 1).16+10).16 + 12
Общая формула, приведенная выше, сама по себе не представляет большого интереса, однако благодаря ей получен очень удобный метод преобразования десятичного числа D в число системы с основанием Ь. Рассмотрим, что произойдет, если правую часть формулы разделить на Ь. Так как первое слагаемое в правой части формулы кратно Ь, то частное от деления равно•
a do — это остаток. Следовательно, do можно вычислить путем деления D на Ь. Более того, частное Q имеет ту же форму записи, что и исходная формула. Поэтому последовательное выполнение операций деления на b приводит к получению последовательных цифр числа D. Ниже приведены некоторые примеры:
179/2 = 89 остаток 1 (цифра младшего разряда)
/2 = 44 остаток 1
/2 = 22 остаток О
/2=11 остаток О
/2=5 остаток 1
/2 = 2 остаток 1
/2=1 остаток О
/2 = 0 остаток 1
-(цифра старшего разряда)
17910 = 101100112
467/8 = 58 остаток 3 (цифра младшего разряда)
/8 = 7 остаток 2
/8=0 остаток 7 (цифра старшего разряда)
46710 = 7238
3417/16 = 213 остаток 9 (цифра младшего разряда)
/16= 13 остаток 5
/16 = 0 остаток 13 (цифра старшего разряда)
341710 = D59le
СЛОЖЕНИЕ И ВЫЧИТАНИЕ
НЕДЕСЯТИЧНЫХ ЧИСЕЛ
При сложении и вычитании недесятичных чисел вручную используются те же методы, которые изучаются в средней школе для выполнения действий над десятичными числами: различие состоит только в том, что применяют другие таблицы сложения и вычитания. Табл. 4.2 представляет собой таблицу сложения и вычитания двоичных чисел. Для того чтобы произвести сложение двух двоичных чисел X и У, вначале мы складываем биты их младших разрядов и исходный перенос свх, равный нулю, получая в соответствии с таблицей значения суммы (х+у+сВх) и переноса свых. Затем мы продолжаем обработку битов справа налево, включая перенос каждой колонки в сумму следующей колонки. Ниже приведены два примера десятичного сложения и соответствующие им примеры двоичного сложения, где переносы показаны в виде последовательности битов С:
С
101111000
X
. 190
10111110
Y
+ 141
+10001101
X^-Y
331
101001011
С
001011000
X
173
10101101
Y
+ 44
+00101100
X-VY
217
11011001
Вычитание двоичных чисел производится аналогичным образом, только вместо переносов используются заемы (Ьвх и ЬВЫх):
В001111100
X_229_11100101
Y4600101110
X— Y18310110111
Операцию вычитания в вычислительных машинах очень часто применяют для сравнения двух чисел. Например, если выполнение операции X—У приводит к возникновению заема из
Таблица 4.2. Таблица сложения и вычитания двоичных чисел
свх* Ьвх
У
свых
х-«-Ьвх
^ВЫХ
0
0
0
0
0
0
0
0
а
1
1
0
1
1
0
1
0
1
0
1
0
Q
1
1
0
1
0
0
1
0
0
1
0
1
1
1
0
1
0
1
0
1
1
1
0
0
1
0
0
1
1
1
1
1
1
1
старшего разряда, то число X меньше числа У; в противном случае число X больше или равно числу У. Использование вычитания для сравнения чисел подробно рассматривается в разд. 8.4 и 8.6.
Таблицы сложения и вычитания могут быть получены для восьмеричных и шестнадцатеричных чисел, а также для чисел с любым другим основанием. Однако лишь у небольшого числа специалистов по вычислительной технике возникнет желание их запомнить. Таблицы вычитания не нужны совсем, так как данную операцию, как показано ниже, всегда можно выполнить, используя систему представления чисел дополнениями и операцию сложения. Даже сложение редко выполняют вручную, особенно при программировании на языке высокого уровня. Программисты же, создающие программы на языке аппаратно-реализованного ассемблера, для облегчения расчетов в двоичной, восьмеричной и шестнадцатеричной системах счисления могут приобрести калькулятор фирмы Texas Instruments, обеспечивающий выполнение операций над шестнадцатеричными числами.
Если батарейки калькулятора выйдут из строя, то можно воспользоваться некоторыми способами выполнения этих операций вручную. В общем случае сложение (или вычитание) любой колонки цифр может быть произведено путем преобразования цифр данной колонки в десятичные, десятичного сложения и последующего преобразования результата с целью получения соответствующих цифр суммы и переноса в недесятичной системе счисления. (Перенос производится каждый раз, когда значение суммы в колонке превышает или равно основанию системы.) Поскольку сложение выполняется в десятичной системе счисления, необходимо знать только таблицу десятичного сложения; единственно, что мы должны дополнительно помнить,— это как осуществляется преобразование десятичных цифр в недесятичные и обратно. Последовательность шагов для сложения
вручную двух шестнадцатеричных чисел приведена ниже:
С
110 0
1
1
0
0
X
+ 1 9 В 916
9
11
9
Y
С 7 Е 61в
^12
7
14
6
X-^Y
Е 1 9 Fle
14
17
25
15
14
16+1
16 + 9
15
Е
1
9
F
4.5. ПРЕДСТАВЛЕНИЕ.
ОТРИЦАТЕЛЬНЫХ ЧИСЕЛ
До настоящего времени мы имели дело только с положительными числами, однако существует также достаточно много способов представления и отрицательных чисел.; При каждодневных расчетах мы обычно пользуемся системой представления чисел в прямом коде, однако в большинстве ЭВМ применяется система представления в виде дополнений. Дальнейшее описание охватывает практически все важнейшие системы представления чисел.
Представление чисел в прямом коде
Наиболее широко используемым способом представления десятичных чисел является прямой код. Числа при таком представлении состоят из величины числа и его знака. Таким образом, десятичные числа +98, —57, +123.5 и —13 интерпретируются как обычно. В десятичной системе в случае отсутствия у числа символа знака число считается положительным, т. е. имеет знак « + >>. Нуль может быть представлен двумя способами: + 0 и —0; однако значения, соответствующие этим представлениям, совпадают. ,
Систему представления чисел в прямом коде довольно просто применить к двоичным числам, если для представления знака ввести дополнительный разряд. Традиционно для этой цели используется старший разряд (плюсу соответствует нулевое значение этого разряда, минусу — единичное). Приведем несколько примеров 8-разрядных двоичных чисел в прямом коде и их десятичных эквивалентов:
001010112 = 4-4310О11111112=4-1271о000000002= +010001010112 = 4-4310О11111112=4-1271о000000002= +010101010112 = — 4310111111112= —12710100000002 = —010101010112 = — 4310111111112= —12710100000002 = —010
001010112 = 4-4310
О11111112=4-1271о
000000002= +010
001010112 = 4-4310
О11111112=4-1271о
000000002= +010
101010112 = — 4310
111111112= —12710
100000002 = —010
101010112 = — 4310
111111112= —12710
100000002 = —010
Система представления в прямом коде содержит одинаковое число положительных и отрицательных чисел. Так, «-разрядное
число в прямом коде может находиться в пределах от —(2П~1— —1) до + (2Л“1—1), причем нуль может быть представлен двумя способами. .
Сложение двух чисел в прямом коде производится по правилам, которые изучались в средней школе. Если знаки чисел совпадают, то их величины складываются, а результату присваивается знак слагаемых. В случае несовпадения знаков из большей величины вычитается меньшая, а результату присваивается знак большей величины. Для того чтобы произвести вычитание чисел в прямом коде, необходимо изменить знак вычитаемого на противоположный и выполнить сложение.
Системы представления чисел дополнениями
В вычислительных машинах отрицательные числа обычно представляются в виде дополнений. Если в системе представления чисел в прямом коде со знаком для получения отрицательного числа достаточно изменить знак положительного числа, то в системах представления чисел дополнениями для получения отрицательного числа необходимо образовать его дополнение. Получить дополнение числа сложнее, чем изменить его знак, однако сложение и вычитание двух чисел в системе с дополнениями может производиться непосредственно, без проверки знаков и абсолютных значений, необходимой в системе представления в прямом коде. Рассмотрим две системы представления чисел дополнениями, одна из которых называется системой представления дополнением до основания системы счисления, а другая — системой представления неполным дополнением до основания системы счисления.
В любой системе с использованием дополнений мы имеем дело с фиксированным количеством цифр, или разрядов, например п. В дальнейшем будем считать, что основанием системы является 6, а числа имеют следующий вид:
D^d^d^^* • 'd^
т. е. точка находится справа и числа являются целыми. Если при выполнении какой-либо операции будет получен результат, для записи которого требуется более п разрядов, то цифры старших разрядов отбрасываются. Если дополнение числа D образовать дважды, то в результате обязательно будет получено число D.
Представление чисел дополнением до основания системы счисления .
В десятичной системе счисления дополнение до основания есть дополнение до десяти. В этой системе дополнение любого n-разрядного числа получается путем его вычитания из числа
10л. Ниже приведены некоторые примеры 4-разрядных десятичных чисел и результаты их вычитания из числа 10000:
Десятичное число
1849
2067'
' 5374
100
8151
Дополнение до десяти
8151
7933
4626
9900
1849
В общем случае дополнение любого n-разрядного числа D до основания b системы счисления может быть получено путем вычитания D из Ьп. Если D находится в пределах от 1 до Ьп—1, то при вычитании получается другое число в тех же пределах. Если D равно нулю, то результат вычитания равен Ьп и имеет вид 100... 00 при общем числе разрядов, равном n+L Отбросив цифру старшего разряда, получим 0. Следовательно, в системе представления чисел дополнением до основания системы счисления существует только одно представление нуля.
Из предыдущих рассуждений можно сделать вывод, что для получения дополнения какого-либо числа до основания системы счисления необходимо выполнить операцию вычитания. Однако ее можно избежать, если Ьп записать как (&Л—1) + 1, а Ьп—D как ((Ьл—1)—D) + l. Число Ьп—1 имеет вид тт-тт, где т = = Ь—1, а число разрядов равно п. Например, 10 000=9 999+1.
Таблица 4.3. Дополнения отдельных цифр
Дополнение
Цифра
восьмерич-шестнадца-
двоичное Ноен десятичное терИчное
0 1
2 3
4 5
6 7
8 9 А В С D Е F
1
0
79F
68Е
57D
46С
35В
24А
139
028
—17
—06
——5
——4
——3
——2
——1
——0
Если для какой-либо цифры d определить дополнение как b—1—d, то (6"—1)—D можно получить путем образования дополнений для всех цифр числа D. Следовательно, дополнение числа D до основания системы счисления получается в результате образования дополнений его отдельных цифр до b—1 и последующего прибавления единицы. В этом нетрудно убедиться на рассмотренных выше примерах дополнений до основания 10. Дополнения цифр, используемых в двоичной, восьмеричной, десятичной и шестнадцатеричной системах счисления, приведены в табл. 4.3. ■
Представление чисел дополнением до двух
Для двоичных чисел дополнение до основания системы счисления называется дополнением до двух. В системе представления дополнением до двух число является положительным, если значение старшего разряда равняется нулю, и отрицательным, если оно равняется единице. Десятичный эквивалент двоичного числа, представленного дополнением до двух, вычисляется так же, как и для числа без знака, за исключением того, что вес старшего значащего разряда в данном случае равен —2"-1, а не +2"-1. Представляемые числа находятся в диапазоне от — (2"-1) до + (2”-1—1). Ниже приведены некоторые примеры 8-разрядных двоичных чисел и их дополнений до двух:
1710= 00010001211101110 (дополнения двоичных+1 Цифр)Ш01Ш2=—171011910 = 01110111210001000 (дополнения двоичных+1 цифр)100010012= — Н91оО1о=ОООООООО211111111 (дополнения двоичных4-1 цифр)11000000002 = 0101710= 00010001211101110 (дополнения двоичных+1 Цифр)Ш01Ш2=—171011910 = 01110111210001000 (дополнения двоичных+1 цифр)100010012= — Н91оО1о=ОООООООО211111111 (дополнения двоичных4-1 цифр)11000000002 = 010—9910 = 10011101201100010 (дополнения двоичных4-1 Цифр)011000112=9910—127,0 = 10000001201111110 (дополнения двоичных4-1 цифр)011111112= 1271О—128ю= 10000000201111111 (дополнения. двоичных4-1 цифр)10000000г= —12810—9910 = 10011101201100010 (дополнения двоичных4-1 Цифр)011000112=9910—127,0 = 10000001201111110 (дополнения двоичных4-1 цифр)011111112= 1271О—128ю= 10000000201111111 (дополнения. двоичных4-1 цифр)10000000г= —12810
1710= 000100012
11101110 (дополнения двоичных
+1 Цифр)
Ш01Ш2=—1710
11910 = 011101112
10001000 (дополнения двоичных
+1 цифр)
100010012= — Н91о
О1о=ОООООООО2
11111111 (дополнения двоичных
4-1 цифр)
11000000002 = 010
1710= 000100012
11101110 (дополнения двоичных
+1 Цифр)
Ш01Ш2=—1710
11910 = 011101112
10001000 (дополнения двоичных
+1 цифр)
100010012= — Н91о
О1о=ОООООООО2
11111111 (дополнения двоичных
4-1 цифр)
11000000002 = 010
—9910 = 100111012
01100010 (дополнения двоичных
4-1 Цифр)
011000112=9910
—127,0 = 100000012
01111110 (дополнения двоичных
4-1 цифр)
011111112= 1271О
—128ю= 100000002
01111111 (дополнения
. двоичных
4-1 цифр)
10000000г= —12810
—9910 = 100111012
01100010 (дополнения двоичных
4-1 Цифр)
011000112=9910
—127,0 = 100000012
01111110 (дополнения двоичных
4-1 цифр)
011111112= 1271О
—128ю= 100000002
01111111 (дополнения
. двоичных
4-1 цифр)
10000000г= —12810
В системе представления дополнением до двух нуль считается положительным, так как в знаковом разряде его представления находится 0. Такая договоренность согласуется с теорией чисел и всеми языками программирования. Так как в системе представления дополнением до двух существует только одно представление нуля, в ней имеется одно «лишнее» отрицательное число —2”-1, у которого нет положительного эквивалента (по абсолютной величине). Как показано в последнем примере, попытка преобразовать такое число в соответствующее ему положительное число приводит к получению исходного числа. Тем не менее, когда при арифметических операциях это число появляется как промежуточный результат, окончательный результат вычислений все же оказывается правильным (если, конечно, не предпринимались попытки получить из указанного числа число с противоположным значением).
Система представления чисел дополнением до двух, или в дополнительном коде, применяется во всех мини- и микро-ЭВМ, а также почти во всех больших ЭВМ, используемых в настоящее время. *
Представление чисел неполным дополнением до основания системы счисления
В системе представления чисел неполным дополнением до основания дополнение n-разрядного числа D получается путем его вычитания из Ьп—1. Дополнение может быть также получено путем образования дополнения отдельных цифр числа D, но без последующего прибавления единицы, как в системе представления дополнением до основания системы счисления. Для десятичной системы счисления такое дополнение называется дополнением до девяти. Ниже приведены некоторые примеры1);
Десятичное число
1849
2067
5374
Дополнение до девяти
8150
7932
4625
7100
8150
2899
1849
1} Кажущаяся противоречивость названий «дополнение до десяти» и «дополнение до девяти» может быть объяснена. Если предположить, что десятичная точка находится непосредственно справа от цифры старшего разряда, то наибольшим числом является число 9.99...9Э; а дополнение числа D до десяти получается в результате его вычитания из числа 10. Дополнение же до девяти получается независимо от положения десятичной точки путем вычитания числа D из наибольшего числа, во всех разрядах которого находится цифра 9.
Представление чисел дополнением до единицы
. Для двоичных чисел неполное дополнение до основания системы счисления называется дополнением до единицы или обратным кодом. Как и в случае представления дополнением до двух, при таком представлении старший разряд является знаковым, и его значение для отрицательных чисел равно единице, а для положительных — нулю. Таким образом, существуют два представления нуля — положительный нуль (00...00) и отрицательный нуль (11...11). Представления положительных чисел в системах с дополнениями до единицы и до двух совпадают, тогда как представления отрицательных чисел отличаются на 1. При вычислении десятичного эквивалента числа, записанного как дополнение до единицы, старшему разряду приписывается вес —(2я*1—1), а не —2П~1. Представляемые числа находятся в диапазоне —(2"-1—1)~+(2п“1—1). Ниже приведены некоторые примеры 8-разрядных двоичных чисел и их дополнений до единицы:
171о = 0ОО1ОО0121191о = О111О1112
111011102= —1710'100010002=—11910
—9910 =100111002—12710 = 100010002
011000112 = 9910011111112= 12710
010 = 000000002 (положительный нуль)
111111112 = О1о (отрицательный нуль)-
Основными достоинствами системы представления дополнением до единицы являются ее симметричность и простота образования дополнений. Однако сложение чисел в такой системе выполняется несколько сложнее, чем в системе представлением дополнением до двух. Кроме того, в машине, производящей операции над дополнениями чисел до единицы, необходимо с помощью аппаратных средств осуществлять две проверки на нуль (так как имеются два представления нуля) или преобразовывать код 11... 11 в 00...00.
Система представления чисел дополнением до единицы нашла Применение в некоторых моделях вычислительных машин фирмы Control Data, однако ею редко пользуются при проектировании новых типов ЭВМ. Тем не менее для некоторых применений, связанных с получением контрольных сумм программным путем (см. разд. 6.3), эта система весьма удобна.
Представление чисел в коде со смещением 2т-1
В системе представления чисел в коде со смешением 2т-х число X, находящееся в диапазоне от —2т-1 до +2т-1—1, записывается в виде /n-разрядного двоичного представления числа
Х+г^-1 (которое всегда неотрицательно и имеет значение меньше 2т). Диапазон чисел, которые могут быть представлены в данной системе, является тем же, что и диапазон чисел, представляемых с помощью /n-разрядных дополнений до двух. Действительно, представления любого числа в обеих системах полностью совпадают, за исключением знаковых разрядов, имеющих всегда противоположные значения. Применение системы представления со смещением 2т“1 будет рассмотрено в разд. 4.11 при изучении систем чисел с плавающей точкой.
4.6. СЛОЖЕНИЕ И ВЫЧИТАНИЕ ЧИСЕЛ В ДОПОЛНИТЕЛЬНОМ КОДЕ
Правила сложения
Таблица 4.4, в которой приведены десятичные числа и их эквиваленты в различных системах представления, позволяет понять, почему для выполнения арифметических операций наиболее предпочтительной является двоичная система представления чисел в дополнительном коде. Если начать счет с числа ЮООг (—8ю) и осуществлять его в прямом направлении, то можно заметить, что каждое последующее число, включая последнее число 01112(+7ю), может быть получено путем прибавления к предыдущему числу единицы без учета каких-либо переносов за пределы четвертого разряда. Этого нельзя сказать о
Таблица 4.4. Десятичные и 4-разрядные двоичные числа
Десятичное число
Дополнительный код
Обратный код
Прямой код
Код со смещением 2т-1
—8
1000
0000
—7
1001
1000
1111
0001
—6
1010
1001
1110
0010
—5
1011
- 1010
1101
ООН
—4
1100
1011
1100
0100
-3
1101
1100
1011
0101
—2
1110
1101
1010
оно
—1
1111
1110
1111
1001
1000
0111
0
0000
или
0000
или 0000
1000
1
0001
0001
0001
1001
2
0010
0010
0010
1010
3
ООП
ООП
ООП
1011
4
0100
0100
0100
1100
5
0101
0101
0101
1101
6
оно
оно
оно
1110
7
0111
0111
0111
1111
числах, представленных в прямом и обратном кодах. Так как обычное сложение является по существу более сложным вариантом счета, то числа, представленные в дополнительном коде, могут складываться по правилам двоичного сложения, но без учета каких-либо переносов за пределы старшего разряда.
Если результат не выходит за границы диапазона чисел, которые могут быть представлены в рассматриваемой системе, сумма всегда будет получаться правильной. Это подтверждается примерами сложения десятичных чисел и соответствующих им 4-разрядных двоичных чисел, представленных в дополнительном коде:
|+3
ООП
, -2
1110
+ +4
+ 0100
+ —6
1010
0111
— 8
1|
1000
+ +6
— 3
оно
+ 1101
+ +4
— 7
+
0100
1001
+3
110011
— 3
1101
4.6.2. Графическое представление чисел
Другой способ рассмотрения системы представления чисел в дополнительном коде связан с использованием «счетчика» 4-разрядных чисел, изображенного на рис. 4.1. Здесь показано
Считание поло- j жительньм чисел JСчитание поло- j жительньм чисел JРис. 4.1. Графическое представление 4-разрядных двоичных чисел в дополнительном коде.Рис. 4.1. Графическое представление 4-разрядных двоичных чисел в дополнительном коде.
Считание поло- j жительньм чисел J
Считание поло- j жительньм чисел J
Рис. 4.1. Графическое представление 4-разрядных двоичных чисел в дополнительном коде.
Рис. 4.1. Графическое представление 4-разрядных двоичных чисел в дополнительном коде.
«круговое», или модульное, представление чисел. Пусть указатель в исходный момент задает какое-либо число. Переместив указатель на п позиций по часовой стрелке, можно прибавить к этому числу +и, где п находится в пределах от 0 до 7. Очевидно, также, что мы можем вычесть п из какого-либо числа,
если переместим указатель на п позиций против часовой стрелки.
Следует отметить, что вычитание п (или прибавление —/г) может быть также выполнено путем перемещения стрелки на 16—п позиций по часовой стрелке. Заметим, что величина 16—п является 4-разрядным дополнением числа п до двух или представлением числа —п. Таким образом, графически подтверждается высказанное ранее утверждение о том, что прибавление отрицательного числа в системе представления в дополнительном коде к другому числу можно выполнить, используя двоичное сложение 4-разрядных представлений этих чисел. Прибавление какого-либо числа эквивалентно перемещению указателя на рис. 4.1 на соответствующее число позиций по часовой стрелке.
Переполнение
Если при выполнении операции сложения получают результат, который выходит за пределы диапазона представляемых чисел, то имеет место переполнение. Сложение двух чисел с разными знаками никогда не приводит к переполнению, чего нельзя сказать о сложении двух чисел с одинаковыми знаками, например:
+ -3
—6
+
1101
1010
+ +5
+ +6
0101
+ ОНО .
—9
1|
0111 = +?
+11
1011 = —5
। —8
1000
, 0111
+ -8
—|—
1000
+ +7
+ 0111
— 16
1|
0000=+0
+ 14
1110=—2
Существует простое правило, позволяющее Выявить наличие переполнения: при сложении переполнение происходит только в том случае, если слагаемые имеют одинаковые знаки, а знак суммы отличается от знака слагаемых. Иногда правило наличия переполнения формулируют, используя понятие переносов, возникающих при выполнении операции сложения: переполнение при сложении происходит в том случае, если значения битов переноса в знаковый разряд и из знакового разряда различны. Внимательное изучение табл. 4.2 показывает, что эти два правила эквивалентны.
" Правило наличия переполнения можно также сформулировать, исходя из анализа рис. 4.1: прибавление к какому-либо числу положительного числа приводит к переполнению, если указатель пройдет границу между позициями +7 и —8.
Большинство ЭВМ для обнаружения переполнения имеют встроенные аппаратные средства.
Правила вычитания
Вычитание чисел в дополнительном коде может производиться так, как если бы они являлись обычными двоичными числами без знака. Можно также сформулировать и соответствующие правила выявления переполнения. Однако непосредственное вычитание чисел в дополнительном коде производится довольно редко. Обычно для того чтобы вычесть одно число из другого, вначале путем образования дополнения до двух получают противоположное значение вычитаемого, а затем складывают его с уменьшаемым, используя обычные правила сложения. Вычитание, реализуемое таким способом, можно выполнить, пользуясь всего одной операцией сложения. Для этого формируют обратный код вычитаемого и складывают его с уменьшаемым при начальном переносе, равном 1, а не 0. -
1—начальный перенос
_ +4 _ 01000100
+3 ООН + ПОР’
4-11|0001'
—начальный перенос
_ 4-з _ ООП ООН.
4-40100 + 1011.
—11111
начальный перенос, .
_ 4-з _ 0011 । 0011
— 41100 + ООН '
4-70111
Наличие переполнения при вычитании можно установить путем изучения знаков уменьшаемого и обратного кода вычитаемого, используя при этом то же правило, что и в случае сложения. Для обнаружения переполнения можно также рассмотреть переносы в знаковый разряд и из знакового разряда и воспользоваться соответствующим правилом, сформулированным для сложения. Последний способ позволяет выявить переполнение без учета знаков уменьшаемого, обратного кода вычитаемого и разности.'
Попытка получения противоположного значения для «лишнего» отрицательного числа приводит (в соответствии с правила-
9-1393
ми, сформулированными выше) к переполнению при прибавлении 1:
— (—8) = —1000=0111
+ 0001
1000 = —8
Тем не менее данное число может использоваться при выполнении вычитания и сложения, если окончательный результат не выходит за пределы допустимого диапазона чисел:
4-4, 0100
+ —8 + 1000
— 41100
1—начальный перенос
_ —311011101
— 81000+ 0111
4-5~1|0101
4.6.5. Числа в дополнительном коде и двоичные числа без знака
Так как числа в дополнительном коде складываются и вычитаются в соответствии с теми же основными алгоритмами, что и числа без знака той же длины, то вычислительной машине для выполнения операций над числами обоих видов необходим всего один тип команды сложения или вычитания. Однако результаты, полученные при выполнении такой команды сложения или вычитания, должны интерпретироваться программой по-разному в зависимости от того, какими числами оперирует машина— числами со знаком (т. е. от —8 до +7) или без знака (от 0 до 15).
На рис. 4.1 было приведено графическое представление системы 4-разрядных двоичных чисел в дополнительном коде. Если внести в рисунок некоторые изменения, то мы получим графическое представление 4-разрядных двоичных чисел без знака (рис. 4.2). Заметим, что двоичные кодовые комбинации занимают здесь те же места, что и на рис. 4.1, а прибавление и вычитание какого-либо числа по-прежнему выполняется путем перемещения указателя на определенное число позиций в соответствующем направлении.
Результат операции сложения выходит за пределы диапазона системы 4-разрядных чисел без знака, приведенной на рис. 4.2, если при перемещении указателя по часовой стрелке он пройдет границу между позициями 15 и 0. В этом случае
говорят о возникновении переноса. Если сложение выполняется с помощью карандаша и бумаги, то фиксируется перенос из старшего разряда.
Результат операции вычитания выходит за пределы диапазона данной системы, если при перемещении указателя против часовой стрелки он пройдет границу между позициями 0 и 15.
Рис. 4.2. ГрафическоеРис. 4.2. Графическоепредставление 4-разрядных двоичных чисел без знака.представление 4-разрядных двоичных чисел без знака.
Рис. 4.2. Графическое
Рис. 4.2. Графическое
представление 4-разрядных двоичных чисел без знака.
представление 4-разрядных двоичных чисел без знака.
В этом случае говорят о возникновении заема. Если вычитание выполняется ручным способом, то эта ситуация отражается как заем, поступающий в старший разряд.
Из рис. 4.2 также следует, что вычитание числа без знака п можно выполнить путем перемещения указателя на 16—п позиций по часовой стрелке. Это эквивалентно прибавлению 4-раз- рядного дополнительного кода числа п. Вычитание приводит к возникновению заема, если при соответствующем прибавлении дополнительного кода не возникает переноса.
В заключение отметим, что при сложении чисел без знака наличие переноса из старшего разряда или заема для данного разряда указывает на то, что результат операции выходит за пределы допустимого диапазона чисел. При сложении чисел со знаком, представленных в дополнительном коде, на возникновение такой ситуации указывает соблюдение условия переполнения, которое было определено ранее. Использование переноса из старшего разряда в данном случае является неуместным в том смысле, что переполнение может произойти независимо от того, имеется перенос или нет.
СЛОЖЕНИЕ И ВЫЧИТАНИЕ ЧИСЕЛ
В ОБРАТНОМ КОДЕ
Для рассмотрения правил сложения чисел, представленных в обратном коде, обратимся к табл. 4.4. Если мы будем вести счет в прямом направлении начиная с числа 1000з(—7ю), то
каждое последующее число в обратном коде получается путем прибавления единицы к предыдущему числу, за исключением перехода от числа ППД—0) к числу 0001г (+ho).
Для продолжения правильного счета при получении числа 1111г необходимо прибавить к нему цифру 2, а не 1. Отсюда вытекает правило сложения чисел в обратном коде: сложение таких чисел сводится к обычному двоичному сложению, но когда осуществляется переход через 1111г, к результату следует прибавить дополнительную 1.
Переход при сложении через 1111г может быть легко зафиксирован по наличию переноса из знакового разряда. Таким образом, можно достаточно просто сформулировать правило сложения чисел, представленных в обратном коде: следует выпол
нить обычное двоичное сложение, а при наличии переноса из знакового разряда прибавить к результату 1. Это правило часто
называют правилом циклического
переноса.
Ниже приведены
примеры сложения чисел
в обратном коде:
+ +3
^ +4
ООП + 0100
+ — 7
0100
+ 1000
+5
4-
0101
1010
+7
0111
— 3
1100
—0
1111
, -2
1101
i +6
оно
. -°
1111
+ -5
+ 1010
+ -3
+ 1100
+ -0
1111
—7
110111
4-з
1]0010
— 0
1|
11110
1
1
1
1000
ООП
1111
Если следовать сформулированному выше правилу двухшагового сложения, то при сложении какого-либо числа с обратным кодом этого числа всегда получается отрицательный нуль. Действительно, выполнение операции сложения в соответствии с этим правилом приводит к получению положительного нуля только в том случае, если оба слагаемых являются положительными нулями.
Некоторые сумматоры позволяют производить сложение чисел, представленных в обратном коде, за один шаг. С этой целью выход переноса старшего разряда сумматора соединяется непосредственно со входом переноса младшего разряда. Если до начала сложения выходной сигнал переноса не будет установлен в нуль, это может вызвать появление неоднозначности или даже неустойчивость системы; в результате может получиться либо положительный, либо отрицательный нуль1). Во
всех остальных случаях при использовании циклического переноса неоднозначности не возникает.
Как и для чисел в дополнительном коде, простейший способ вычитания чисел в обратном коде состоит в том, чтобы получить дополнение вычитаемого и выполнить сложение. Правила обнаружения переполнения при сложении и вычитании чисел в обратном коде являются такими же, как и для чисел в дополнительном коде. Операции над такими числами могут выполняться на вычислительных машинах, предназначенных для работы с дополнительными кодами, путем использования команд «Сложение с переносом» и «Вычитание с переносом».
ДВОИЧНОЕ УМНОЖЕНИЕ
В данном разделе рассматриваются алгоритмы умножения чисел со знаком и без знака. Многие вычислительные машины позволяют реализовать эти или подобные алгоритмы в машинных командах. Однако в малых ЭВМ с целью экономии аппаратных средств такие команды обычно не предусматриваются, поэтому алгоритмы умножения реализуют программным способом с использованием простых операций сложения и сдвига.
Для описания алгоритмов в данном и последующих разделах книги используется расширенный язык программирования Паскаль, который позволяет производить обращение к отдельным разрядам слова. Полный список дополнительных функций этого языка над данными типа bit приведен в приложении Б. Рассматриваемые здесь алгоритмы могут быть легко транслированы на язык ассемблера большинства ЭВМ.
Умножение чисел без знака
В средней школе мы изучали, что умножение чисел производится путем сложения ряда сдвинутых относительно друг друга множимых, с учетом цифр множителя. Как показано ниже, этот способ можно также использовать и для получения произведения двух двоичных чисел без знака.
11 *
1011 Множимое
_ ,х 1101 Множитель
х 13
1011
33.
0000 Сдвинутые
и
1011 'множимые
143
1011.
10001111 Произведение
Формирование сдвинутых множимых при двоичном умноже-1 нии выполняется довольно просто, так как возможными цифрами множителя могут быть только 0 и 1. ’
Вместо получения всех сдвинутых множимых и последующего их сложения более удобным оказывается прибавление каждого сдвинутого множимого по мере его формирования к частичному произведению. Пример умножения с использованием этого метода приведен в табл. 4.5.
Таблица 4.5. Умножение с использованием частичных произведений
1011 Множимое
х 1101 Множитель
00000000 Частичное произведение
1011 Сдвинутое множимое
00001011 Частичное произведение 0000
00001011 Частичное произведение
1011 Сдвинутое множимое
00110111 Частичное произведение
ЮН Сдвинутое множимое
10001111 Произведение
Когда в ЭВМ производится умножение /n-разрядного слова на n-разрядное, для представления окончательного произведения требуется самое большее п+т разрядов. Поэтому типичный алгоритм умножения позволяет перемножить два п-разряд-
PROGRAM UnsignedMultiply (input,output);
CONST wlen = 15;
TYPE word - ARRAY [wlen;:0] OF bit;
VAR ent : integer; mpy, mend, hiProd, loProd : word;
BEGIN
read (mpy,mend); loProd :* 0; hiProd :- 0; {initialization!
FOR ent :» 0 TO wlen DO
BEGIN {Process the multiplier one bit at a time.}
{If multiplier LSB is 1, add multiplicand to high-order product, setting BC according to carry from MSB.}
• IF mpy[0] « 1 THEN hiProd := Badd (hiProd,mend,0) ELSE BC := 0;
{Shift hiProd right, put BC into MSB, and put lost LSB into BC.} hiProd := Bshr (hiProd,BC);
{Shift loProd right, picking up LSB that was lost from hiProd.) loProd := Bshr (loProd,BC) ;
{Now move the next bit of multiplier to the LSB position.)
mpy := Bshr (mpy, 0);
END;
writeln(hiProd,loProd);
END*
Рис. 4.3. Программа умножения двух 16-разрядных целых чисел без знака.
ных слова и получить 2п-разрядное двойное слово произведения. На рис. 4.3 приведен алгоритм умножения двух положительных целых чисел тру и mend, в результате реализации которого получается произведение в виде целого положительного числа, представленного переменными hiProd и loProd.
В программе, приведенной на рис. 4.3, операции над отдельными битами описываются с помощью расширенной версии языка Паскаль. Операции, записанные на этом языке, выпол-
(Х,С)(Х,С)
(Х,С)
(Х,С)
а56
Рис 4.4. Функции языка Паскаль, позволяющие выполнять операции над битами:
а — Bshl(x : bitarray; с : bit); б — Bshr(x : bitarray; с : bit); в — Badd(x,y : bitarray; с : bit).
няются непосредственно. Переменные типа bit могут принимать значения 0 и 1. Переменная word определяется как массив, состоящий из переменных типа bit, пронумерованных слева направо — от wlen до 0. Функция Bshl (х,с) приводит к сдвигу массива х, состоящего из переменных типа bit, на один разряд (рис. 4.4,а). В освободившийся старший разряд поступает бит переноса с; содержимое младшего разряда помещается на место глобальной переменной ВС типа bit (рис. 4.4,6). Функция Badd(x,y,c) (рис. 4.4, в) позволяет произвести сложение двух массивов х и у и бита начального переноса с по правилам двоичного сложения, рассмотренным в разд. 4.4. Если при выполнении этой операции возникает перенос из старшего разряда, то ВС устанавливается в 1; в противном случае ВС принимает значение, равное 0.
О ходе выполнения программы умножения дает представление рис. 4.5. Здесь, как и в рассмотренном выше примере умножения (табл. 4.5), сначала обрабатываются младшие биты, множителя. Однако вместо сдвига множимого влево оно помещается под старшим словом частичного произведения и производится сдвиг частичного произведения вправо.
Число повторений цикла в программе, приведенной на рис. 4.3, равно числу разрядов множителя. В каждом цикле при значении младшего разряда тру, равном 1, производится при-
бавление множимого mend к переменной hiProd. В результате сложения может возникнуть перенос, который установит ВС в 1. Если прибавления не производится, то осуществляется сброс ВС. Выполняя описанные операции, можно произвести сдвиг старших битов получаемого частичного произведения (hiProd и ВС) на один разряд вправо, а младший бит сдвинуть в loProd. После того как бит произведения был сдвинут в loProd, его значение больше не изменяется.
Рис. 4.5. Переменные, используемые в алгоритме умножения.
Перед началом умножения данные в loProd отсутствуют, в то время как в тру находятся биты множителя, подлежащие обработке. После окончания умножения в loProd будут содержаться младшие биты произведения, а тру будет очищен. Тщательное изучение программы позволяет сделать вывод о том, что переменные тру и loProd могут быть объединены в одну переменную. Действительно, такой прием находит применение в большинстве умножителей, реализованных аппаратным способом, что позволяет уменьшить число используемых схем.
Умножение чисел со знаком
Умножение чисел со знаком можно выполнить, используя умножение чисел без знака и обычные правила, которые изучаются в средней школе: следует умножить величины чисел и сделать произведение положительным, если знаки сомножителей совпадают, или отрицательным, если знаки разные. Этот способ очень удобен для умножения чисел в прямом коде.
В системе представления чисел в дополнительном коде для получения величины отрицательного числа требуется выполнить
операцию образования дополнения, а если должен быть полу» чен отрицательный результат, то необходимо образовать дополнение произведения без знака. Такие сложности приводят к необходимости поиска более эффективного способа умножения чисел, представленных в дополнительном коде.
PROGRAM SignedMultiply (input,output);
CONST wlen = 15;
TYPE word = ARRAY [wlen::0] OF bit;
VAR ent : integer; mpy, mend, hiProd, loProd : word;
BEGIN
read (mpy, mend); loProd := 0; hiProd := 0;. {Initialization}
FOR ent := 0 TO wlen-1 DO
BEGIN {Process all mpy bits except MSB.}
{If multiplier LSB is 1, add multiplicand to high-order product, set BV to 1 on two1s-complement overflow.}
IF mpy[0] = 1 THEN hiProd := Badd(hiProd,mend,Q) ELSE BV := 0;
{Shift hiProd right, replicate the correct sign of the result', and put the lost LSB into BC.}
hiProd := Bshr (hiProd,Bxor(hiProd[wlen],BV));
{Shift loProd right, picking up LSB that was lost from hiProd.} loProd := Bshr(loProd,BC);
(Move the next bit of mpy to the LSB position.} mpy : = Bshr (mpy,0) ;
END;
{Process the sign bit of mpy.}
{If mpy’s LSB is 1, subtract multiplicand from high-order product, setting BV to 1 on two's-complement overflow.}
IF mpy[0] = 1 THEN hiProd := Badd(hiProd,Bcom(mend), 1) ELSE BV := 0; {Shift hiProd right, give it the correct sign,, and put LSB into BC.} hiProd := Bshr(hiProd,Bxor(hiProd[wlen] ,BV));
{Shift loProd right, picking up the LSB that was lost from hiProd.} loProd := Bshr(loProd,BC);
writein (hiProd,loProd);
END.
Рис. 4.6. Программа умножения двух 16-разрядных целых чисел в дополнительном коде.
В общем случае умножение чисел без знака выполняется как последовательность операций сложения без знака над множимыми, сдвинутыми относительно друг друга. Величина сдвига множимого для каждого шага соответствует весу разряда множителя. Разряды множителя, представленного в дополнительном коде, обладают тем же весом, что и разряды множителя без знака, за исключением старшего разряда, вес которого является отрицательным. Из этого следует, что умножение чисел в дополнительном коде может выполняться в виде последовательности операций сложения над сдвинутыми относительно друг друга множимыми, за исключением последнего шага, в качестве которого используется вычитание. Тогда становится воз-
можным получить алгоритм умножения чисел в дополнительном коде (рис. 4.6).
В расширенном языке Паскаль глобальная переменная BV типа bit устанавливается после каждой операции Badd в соответствии с тем, имеется перенос или нет. Функция Bxor(c, d) предназначена для выполнения операции Исключающее ЙЛЙ над 1-битовыми величинами с и d; она позволяет получить с, если d = 0, или дополнение с, если d=l. Так как переменная BV устанавливается в 1 только в том случае, если результат выполнения функции Badd имеет неправильный знак, то выражение Bxor (hiProd[wlen]!,BV) всегда приводит к получению правильного знака результата.
Функция Всот(х) служит для образования поразрядного дополнения массива х. Таким образом, с помощью функции Badd(hiProd,Bcom(mend), 1) в последней части программы производится вычитание в дополнительном коде mend из hiProd.
Читателям предлагается исследовать возможности алгоритма, приведенного на рис. 4.6, рассмотрев несколько примеров с использованием 4-разрядных чисел в дополнительном коде. Подобно алгоритму умножения без знака, алгоритм умножения со знаком может быть модифицирован с целью использования одной и той же переменной для тру и loProd.
ДВОИЧНОЕ ДЕЛЕНИЕ
Деление чисел без знака
Простейший алгоритм двоичного деления (табл. 4.6) также основан на методе деления, который изучают в средней школе. Как при десятичном, так и при двоичном делении с целью определения числа, кратного сдвинутому делителю и подлежащего вычитанию, мы мысленно сравниваем уменьшенное делимое с числами, кратными делителю. В случае десятичного деления (см. табл. 4.6) мы сначала выбираем число 11 как наибольшее число, кратное И, которое меньше 21, а затем выбираем число 99 как наибольшее число, кратное 11, которое меньше 107. В случае двоичного деления выбор чисел производится несколько проще, так как выбрать можно только нуль или сам делитель. Однако для того, чтобы выбрать правильный сдвинутый делитель, здесь требуется выполнить операцию сравнения.
В центральных процессорах деление без знака дополняет операцию умножения. Для типичного алгоритма деления делимым является двойное слово, а делителем одинарное; частное и остаток получаются в виде одинарных слов. Если при выпол- иении такого деления окажется, что делитель равен нулю, или
для представления частного потребуется более одного слова, то происходит переполнение. Вторая ситуация имеет место только в том случае, если делитель меньше или равен старшему слову делимого.
PROGRAM UnsignedDivide (input, output) ;
CONST wlen = 15;
TYPE word = ARRAY [wlen::0] OF bit;
VAR hiDvnd, loDvnd, dvsr, quot, rmdr : word;
ent : integer; flag : boolean;
BEGIN
read(hiDvnd,loDvnd,dvsr);
IF Beq(dvsr,0) THEN writein ('Error, division by 0 not allowed1)
ELSE IF Bls(dvsr,hiDvnd) THEN writein('Error, quotient too big1)
ELSE
BEGIN
quot := 0;
FOR ent := 0 TO wlen DO
BEGINi
quot := Bshl(quot,0) ; {Make room for next quotient bit.} loDvnd := Bshl (loDvnd,0); hiDvnd := Bshl (hiDvnd,BC); {Shift double-word dividend left one bit, old MSB ends up in BC.}
flag := BC=1; {Set flag if shifted dividend definitely greater than divisor.}
hiDvnd := Badd (hiDvnd,Bcom(dvsr),1); {Do trial subtraction* set BC if the result is positive.}
IF flag OR (BC=1) THEN
quot[0] := 1 {Positive result, set quotient bit.}
ELSE hiDvnd := Badd(hiDvnd,dvsr,0); {Negative result, }
END;I restore hiDvnd. }
rmdr := hiDvnd; writein(quot,rmdr);.
END;
END.
Рис. 4.7. Программа деления двух целых чисел без знака.
На рис. 4.7 приведен алгоритм деления, который для большинства микропроцессоров может быть легко преобразован в программу на языке ассемблера. Этот алгоритм позволяет разделить целое число без знака hiDvnd, loDvnd на делитель dvsr и получить частное quot и остаток rmdr в виде одинарных слов. Использование переменных показано на рис. 4.8.
Алгоритм начинается с проверки двух условий переполнения, выполняемой с помощью функций Beq и Bls расширенной версии языка Паскаль. Функция Beq(x, у) имеет значение «истинно», если ее аргументы, представляющие собой массивы переменных типа bit, совпадают, и значение «ложно» в противном
Таблица 4„6. Пример длинного деления
19 пУш
11 107
99 8
10011 Частное 1011)11011001 делимое
ЮНсдвинутый делитель
0Ю1Уменьшенное делимое
0000 Сдвинутый делитель
1010 Уменьшенное делимое 0000 Сдвинутый делитель
10100 Уменьшенное делимое 1011 сдвинутый делитель
10011 Уменьшенное делимое
1011 Сдвинутый делитель 1000 Остаток
случае. Функция Bls(x,y) принимает значение «истинно», если х меньше или равно у, а оба аргумента интерпретируются как числа без знака.
Если переполнения не происходит, то частное устанавливается в 0 и выполняется цикл деления, который повторяется по одному разу для каждого разряда частного (т. е. 16 раз в случае 16-разрядного частного). При каждом входе в цикл известно, что переменная hiDvnd меньше делителя dvsr. Поэтому сдвиг двойного слова делимого на один разряд влево приводит к тому, что 17-разрядное слово, состоящее из переменных ВС и hiDvnd, все еще остается меньше 2-dvsr. Если эта величина больше или равна делителю dvsr, то в частное записывается 1 и производится вычитание dvsr, в результате которого переменная hiDvnd вновь становится меньше делителя dvsr. (Функция сдвига Bshl была показана на рис. 4.4, а.)
Здесь встает вопрос о сравнении переменной ВС, hiDvnd и делителя dvsr. Флажок flag принимает значение «истинно», если ВС=1. Когда флажок имеет значение «истинно», то переменная ВС,hiDvnd является полным 17-разрядным числом, которое заведомо больше 16-разрядного числа dvsr. В любом случае при выполнении пробного вычитания, реализуемого с помощью операции Badd(hiDvnd,Bcom(dvsr),l), производится вычитание dvsr из hiDvnd. Так как заем является дополнением переноса, то при отсутствии заема из старшего разряда эта операция приводит к установке ВС в 1; в противном случае ВС устанавливается в 0. Если флажок имеет значение «истинно» или отсутствует заем, то ВС,hiDvnd больше, чем dvsr, и в частное
записывается 1. В противном случае перед выполнением операции необходимо восстановить значение переменной hiDvnd, прибавив к ней делитель dvsr.
При каждом повторении цикла переменная quot сдвигается на один разряд влево, чтобы освободить место для очередного бита частного. Величина, оставшаяся в hiDvnd после 16-кратного повторения цикла, является остатком.
Рис. 4.8. Переменные, используемые в алгоритме деления.
При выполнении алгоритмов деления, как и алгоритмов умножения, на место одной из переменных записывается результат, в то время как место другой переменной освобождается. Поэтому в качестве переменных quot и loDvnd можно использовать одну и ту же переменную. Этот прием часто реализуется аппаратным способом. Для лучшего понимания алгоритма деления читателю рекомендуется рассмотреть несколько примеров и особых случаев с использованием 4-разрядных чисел.
Приведенный выше алгоритм деления обычно называют алгоритмом деления с восстановлением остатка. Существует также алгоритм деления без восстановления остатка, в цикле деления которого отсутствует шаг сложения, обеспечивающий восстановление остатка. Благодаря большей скорости реализации при построении схем делителей используется обычно последний алгоритм. Однако в программах, написанных на языке ассемблера, алгоритм без восстановления остатка в лучшем случае позволяет лишь незначительно увеличить производительность по сравнению с алгоритмом с восстановлением остатка, поэтому он рассматриваться не будет.
Деление чисел со знаком
Как и в случае умножения, существует несколько методов, позволяющих выполнять деление непосредственно над числами, представленными в дополнительном коде. Эти методы часто
PROGRAM SignedDivide (input,output);
CONST wlen = 15;
TYPE word = ARRAY [wlen::0] OF bit;
VAR hiDvnd, loDvnd, dvsr, quot, rmdr : word;
Cnt : integer; negDvsr, negDvnd : boolean;
BEGIN ’
read(hiDvnd,loDvnd,dvsr) ;
negDvsr := dvsr[wlen]=1; negDvnd := hiDvnd[wlen]=1;
IF negDvsr THEN dvsr := Badd(0,Boom(dvsr),1); {Make dvsr positive.}
IF negDvnd THEN {Make dvnd positive.}
BEGIN {A 'double precision' negate.}
loDvnd : = Badd (0, Bcom (loDvnd) , 1) ;
hiDvnd := Badd(0,Bcom(hiDvnd),BC);
END;
IF Beg(dvsr,0) THEN writeln('Error, divide by 0 not allowed')
ELSE JF Bls (dvsr,hiDvnd) THEN writein('Error, quotient too big')
ELSE *
BEGIN {Compute quotient and remainder.}
qqot := 0;
FOR ent := 0 TO wlen-1 DO
BEGIN
quot := Bshl (quot, 0) ; {Make room for next quotient bit.}
v {Shift double-word dvnd left one bit.}
loDvnd := Bshl (loDvnd,0); hiDvnd := Bshl (hiDvnd,BC) ;
hiDvnd := Badd(hiDvnd,dvsr,1); {Do trial subtraction.}
IF hiDvnd[wlen]=0 THEN
quot[0] := 1; {Positive result, set quotient bit.}
ELSE hiDvnd := hiDvnd + dvsr; {Negative — restore hiDvnd.} END;
it* negDvnd THEN rmdr := Badd(0,Bcom(hiDvnd),?)
ELSE rmdr := hiDvnd; {Remainder gets sign of dividend.}
IP (negDvsr AND (NOT negDvnd)) OR
((NOT negDvsr) AND negDvnd) THEN quot := Badd (0,Bcom (quot),1); writein (quot, rmdr) ;
ENDi END.
Рис. 4.9. Программа деления целых чисел, представленных в дополнительном коде.
реализуются схемным путем. Однако для программ, написанных на языке ассемблера, простейший способ деления чисел в дополнительном коде заключается в преобразовании этих чисел в положительные, выполнении деления с восстановлением остатка и последующем приведении результата к соответствующему знаку.
Алгоритм деления с восстановлением остатка в данном случае является упрощенным, поскольку отрицательные числа, получаемые в результате пробных вычитаний, теперь могут быть представлены в пределах длины слов делимого и делителя. Приспосабливая рассмотренный выше алгоритм для деления чисел со знаком, необходимо учесть, что делитель, остаток и частное состоят из 15 разрядов и знака, а делимое — из 31 разряда и знака. Новая программа приведена на рис. 4.9. Вначале осуществляется проверка знаков делителя и делимого и оба операнда в соответствии с алгоритмом деления делаются положительными. В конце реализации алгоритма результат приводится к соответствующему знаку. Преобразования выполняются таким образом, чтобы знак остатка был таким же, как и знак делимого. Тогда если остаток прибавить к делителю, умноженному на частное, то получится исходное делимое.
ДВОИЧНО-ДЕСЯТИЧНАЯ СИСТЕМА ПРЕДСТАВЛЕНИЯ ЧИСЕЛ
В двоично-десятичной системе десятичные цифры от 0 до 9 представляют 4-разрядными двоичными комбинациями от 0000 до 1001; комбинации от 1010 до 1111 не используются. Преобразования из двоично-десятичной системы в десятичную систему (и обратные преобразования) не вызывают затруднений и выполняются путем прямой замены четырех двоичных цифр одной десятичной цифрой (или обратной замены). Две двоичнодесятичные цифры составляют 1 байт. Таким образом, с помощью 1 байт можно представлять значения от 0 до 99, а не от 0 до 255, как при использовании 8-разрядного двоичного числа без знака. Используя 1 байт для представления каждых двух десятичных цифр, можно формировать двоично-десятичные числа с любым требуемым числом десятичных разрядов.
Для представления отрицательных двоично-десятичных чисел, как и отрицательных двоичных чисел, могут использоваться различные системы. Числа со знаком в двоично-десятичной системе имеют один дополнительный разряд для знака. Широко используется представление двоично-десятичных чисел как в прямом коде, так и дополнением до десяти. В первом случае кодирование знака является произвольным, а во втором — плюсу соответствует кодовая комбинация 0000, а минусу—1001.
Сложение* двоично-десятичных чисел, имеющих один десятичный разряд, выполняется так же, как и сложение 4-разряд- ных двоичных чисел без знака, за исключением того, что при получении результата, превышающего 1001, необходимо производить коррекцию. Результат корректируется путем прибавле-
ния двоичного кода числа 6. Приведем примеры сложения:
40100 б0101
+ 5 + 0101+ 9 + 1001
91001141110
~^~ ОНО—коррекция
10+4110100
50101. 91001
+ 11+ 10119^1001
1611000018110010
*" ОНО—коррекция'ОНО—коррекция
10+611011010+811 1000
Заметим, что если первоначальное двоичное сложение или прибавление корректирующего числа приводят к возникновению переноса, то при сложении значений двух десятичных разрядов двоично-десятичных чисел осуществляется перенос в следующий десятичный разряд.
Несмотря на то что память при двоично-десятичном представлении чисел используется менее эффективно, чем при двоичном представлении, первая система нашла широкое применение во многих устройствах ввода-вывода десятичных данных, например в кассовых аппаратах, так как позволяет избежать преобразований в двоичную систему и обратно при каждой операции ввода-вывода. Многие ЭВМ имеют полный набор команд для обработки данных в двоично-десятичной системе, в том числе сложение, вычитание и преобразование в двоичную систему и обратно. В других ЭВМ предусмотрена команда «Десятичная коррекция», которая позволяет приспособить двоичные операции для обработки десятичных данных. Примеры операций над числами со знаком и без знака в двоично-десятичном представлении, выполняемых с помощью обычных команд микро-ЭВМ, будут приведены в разд. 8.11.
ПРЕДСТАВЛЕНИЕ ЧИСЕЛ
В ФОРМАТАХ С ФИКСИРОВАННОЙ И ПЛАВАЮЩЕЙ ТОЧКАМИ
Представление чисел в формате с фиксированной точкой
Во всех системах представления чисел, которые рассматривались до сих пор, двоичная точка располагается справа от правого крайнего разряда. Таким образом, значения 16-разрядных
двоичных чисел без знака находятся в пределах от 0 до +65 535:
0000000000000000.2= 010
1100000000000000-2 = 4914210
0010000000000000-2 =819210
1000000000000000.2 = 327681е
11111111111 11111.2=655351о
0000000000000001-2 =110
Такой тип системы представления чисел лучше всего подходит для программ, которые служат для подсчета объектов или, другими словами, оперируют целыми величинами.
Однако во многих программах используются дробные величины. Например, в программе, предназначенной для составления таблицы положительных синусов, может оказаться удобным расположить двоичную точку справа от левого крайнего разряда 16-разрядного числа, как показано ниже:
0.0000000000000002 = 010
1.1ОООООООООООООО2 = 1.510
О.ОЮОООООООООООО2 = О.251о
1.0000000000000002 = 1.010
1.1111111111111112=1.999 969 482 421 87510
0.0000000000000012 = 0.000 030 517 578 12510
Как мы выяснили в разд. 4.1, веса разрядов, расположенных справа от двоичной точки, являются отрицательными степенями числа 2. Расположение двоичной точки в последнем примере обеспечивает максимально возможное число двоичных разрядов для более точного представления значений синуса, меньших единицы, и в то же время позволяет представить максимальное значение синуса (1.0).
Между числами двух приведенных выше примеров существует определенная связь. Так, если X— это число из первого примера, ах — соответствующее число из второго примера, то х=Х-2-15, где 2~15— неявный масштабный коэффициент. В общем случае масштабный коэффициент системы представления может быть любым, например 2+15, 2~25, 3.14159, 109 или 186000-60-60-24-365. Использование каждого из этих коэффициентов может оказаться приемлемым для того или иного применения. Однако наиболее предпочтительно использовать степени числа 2, так как умножение или деление на масштаб-
ный коэффициент в этом случае выполняется путем перемещения двоичной точки.
Сложение и вычитание чисел с неявными масштабными коэффициентами производится по правилам обычного двоичного сложения и вычитания:
хХ.2-15
+ У21^15
x-Vy(Х^.2^
Однако при выполнении умножения и деления требуется осуществлять коррекцию масштабных коэффициентов:
хХ-2"15
Х у Х У-2-15
х-у(Х.У.2-15).2"16
Системы представления, в которых положение двоичной точки фиксировано, а программист должен следить за изменением масштабных коэффициентов, называются системами представления с фиксированной точкой. Так как с использованием неявных масштабных коэффициентов, отличных от единицы, связаны дополнительные вычисления, то практически во всех аппаратных и программных средствах ЭВМ для чисел с фиксированной точкой применяется масштабный коэффициент, равный 1, так что двоичная точка, как мы и считали ранее, находится справа от числа.
Независимо от значения масштабного коэффициента «динамический диапазон» системы представления чисел с фиксированной точкой является ограниченным. Например, в 16-разряд- ной системе представления чисел с фиксированной точкой отношение наибольшего числа к наименьшему (отличному от нуля) составляет 216 даже в том случае, если наибольшее число равно 2100.
Представление чисел в формате с плавающей точкой
Чтобы исключить процесс слежения за изменением значений масштабных коэффициентов, который может приводить к ошибкам, и обеспечить представление большого диапазона чисел с помощью ограниченного числа двоичных цифр, для кодирования масштабных коэффициентов чисел можно использовать представление с плавающей точкой. Например, мы можем разбить 16-разрядное слово на два поля (рис. 4.10). Величина числа X в этом формате определяется как М-2Е, где М и Е представлены как двоичные числа без знака. Величина М является
мантиссой числа, а 2В— масштабным коэффициентом, где Е есть порядок числа. Рассматриваемая форма записи является двоичным эквивалентом десятичного представления, при котором числа записываются следующим образом: 34-1029, 186-103 и т. д.
151510 910 9ООПорядок (5)Порядок (5)Мантисса (М)Мантисса (М)
15
15
10 9
10 9
О
О
Порядок (5)
Порядок (5)
Мантисса (М)
Мантисса (М)
Рис. 4.10. Шестнадцатиразрядное представление с плавающей точкой.
Некоторые двоичные числа и их десятичные эквиваленты в рассмотренной системе представления с плавающей точкой приведены ниже:
■ Е М
000000 00000000002=0.2» = 01о■
000000 10000001012 = 517.2» = 51710
001000 01001011012= 301 -2« = 77 О561о
111111 11111111112= 1023- 26Э=9 423 991 637 655 520 332 9241е
Располагая 16 разрядами, можно представлять числа очень большой величины — более девяти секстильонов. Но отведя шесть разрядов под поле порядка, мы тем самым уменьшили разрешающую способность, или точность, системы представления с 16 двоичных разрядов до 10. Теперь вероятность того, что мы сможем точно представить какое-либо вещественное число, находящееся между 0 и девятью секстильонами, является очень низкой. Если мы возьмем целое число, которое может быть представлено точно и которое является ближайшим к этому вещественному числу, то типичная погрешность будет составлять 1/211. Для сравнения, используя 16-разрядную систему представления целых чисел в формате с фиксированной точкой, для любого данного вещественного числа в пределах от 0 до 216—1 мы можем выбрать целое число, отличающееся от него не более чем на 0,5, причем погрешность для наибольшего числа будет составлять 1/217. Кроме того, в 16-разрядной системе с плавающей точкой можно представить менее 216 отдельных чисел. Например, нуль в этой системе может быть записан 64 различными способами (для этого подходят любые значения порядков), а другие числа могут иметь до девяти различных форм записи (например, 1536ю=3.29 = 6.28 = .... = 768-21). Тем не менее система представления с плавающей точкой является
полезной для использования, если ее дополнить следующим образом:
Для получения достаточной точности увеличить число разрядов мантиссы.
Для представления как положительных, так и отрицательных чисел отвести один разряд под знак мантиссы.
Для увеличения динамического диапазона увеличить число разрядов порядка.
Для представления дробных чисел допустить использование как положительных, так и отрицательных порядков.
Использование формата с плавающей точкой в типичной ЭВМ
В ЭВМ разного типа для представления чисел используются различные форматы с плавающей точкой. Это связано с выбором длины мантиссы и порядка, положения точки в мантиссе,
313023 НВ 22о
| | Порядок (Е)|Мантисса (М)|
Знак мантиссы
(MS)
Рис. 4.11. Представление с плавающей точкой, используемое в мини-ЭВМ PDP-11.
способа кодирования отрицательных чисел и с использованием «скрытого» разряда. На рис. 4.11 показан формат, принятый разработчиками мини-ЭВМ PDP-11. Здесь мантисса М представляет собой 24-разрядное число без знака (для указания знака мантиссы служит разряд НВ) с двоичной точкой, расположенной непосредственно слева от «скрытого» разряда НВ (что такое «скрытый» разряд НВ будет пояснено ниже). Таким образом, наименьшей мантиссой является число .00...00 = 0, а наибольшей — число .11...11, которое меньше единицы всего лишь на 2-24. Разряд MS служит для указания знака мантиссы. Если значение этого разряда равно 0, то мантисса является положительной, а если 1 — отрицательной. Таким образом, MS и М образуют условное 25-разрядное число в прямом коде.
Порядок Е является 8-разрядным числом со смещением 128 (см. разд. 4.5.7), так что комбинации двоичных цифр от 00000000 до 11111111 служат для представления значений порядков от —128ю до +127ю соответственно. Выбор представления со смещением 128, как мы вскоре увидим, наилучшим образом подходит для упорядочения чисел.
Некоторые числа в формате, используемом в PDP-11, могут быть записаны многими способами, например:
MS ЕМ'
+ 1 = 0 10000001 100000000000000000000000
(+1)(0.5)
+ 1 = о 10000010 010000000000000000000000
(+2)(0.25)
+ 1=о 10000011 001000000000000000000000(+3)(0.125)
Однако в большинстве ЭВМ. для каждого числа принято использовать одно нормализованное представление, которое является наиболее предпочтительным. Число с плавающей точкой, отличное от нуля, считается нормализованным, если значение старшего разряда мантиссы отлично от нуля. Таким образом, в приведенном выше примере наиболее предпочтительной является первая форма записи числа +1. Нормализованным представлением нуля является такое представление, при котором во всех разрядах находятся нули.■
В соответствии с договоренностью, мини-ЭВМ PDP-11 обрабатывает только нормализованные числа. Поэтому в первом (старшем) разряде мантиссы всегда находится 1. Когда в PDP-11 производится запись числа с плавающей точкой в какой-либо регистр или ячейку памяти, этот разряд (обозначенный как НВ на рис. 4.11) отбрасывается. При выполнении операций над числами с плавающей точкой считается, что значение разряда НВ равно 1. Таким образом, 33 бит формата, показанного на рис. 4.11, упаковываются в 32 бит памяти.
Для нормализованных чисел наименьшим значением мантиссы является 0,5, а не 0, в то время как наибольшее значение остается равным 1—2~24. Чтобы обеспечить представление нуля, для PDP-11 считается, что значение разряда НВ равно нулю, если порядок равен 00000000 (—128). Ниже приведены некоторые десятичные числа и их нормализованные эквиваленты:
MS
Е
НВ
м
+3.000=0
10000010
1
10000000000000000000000
(+2)
(0.75)
— 17.375=1
10000101
1
00010110000000000000000
(+5)
(0.54296875)
+0.125=0
01111110
1
00000000000000000000000
(-2)
(0.5)
+0.000 = 0
00000000
0
00000000000000000000000
(-128)
(0.0)
Если два числа X и Y являются нормализованными, то X больше У, если порядок X больше порядка У (см. упр. 4.31). Соотношение между числами X и У зависит от значения мантисс только в том случае, если порядки чисел совпадают. Таким образом, при использовании полей MS, Е и М (рис. 4.11) становится возможным сравнивать 32-разрядные нормализованные числа с плавающей точкой, как будто это 32-разрядные числа в прямом коде.
Форматы представления чисел с плавающей точкой других ЭВМ являются, как правило, 32-разрядными; возможно использование большего числа разрядов в случае объединенных полей мантиссы, порядка и знака. Тридцатидвухразрядный формат представления чисел с плавающей точкой обычно называется форматом с одинарной точностью.
Комитет стандартов Института инженеров по электротехнике и радиоэлектронике рекомендует к использованию 32-раз- рядный формат с плавающей точкой, подобный тому, который применяется в ЭВМ PDP-11. В предложенном формате двоичная точка расположена справа от разряда НВ. Таким образом, мантисса может принимать значения между 1 и 2. В случае нормализованных чисел для порядков используется представление со смещением 127, при этом диапазон правильных порядков включает значения от —126 (000000012) до +127 (111111102). Максимальный и минимальный порядки (000000002 и 111111112) служат для указания необычных (исключительных) условий, возникающих при выполнении программы. Комитет стандартов предлагает также 64-разрядный формат (с удвоенной точностью) и дает рекомендации по применению расширенных форматов (например, 128-разрядного формата с учетверенной точностью).
Во многих вычислительных машинах фирмы IBM используется 32-разрядный формат с плавающей точкой, который существенно отличается от рассмотренных здесь форматов: порядок в нем определяет показатель степени числа 16, а нс числа 2. Формат включает в себя 7-разрядный порядок в представлении со смещением 64, разряд знака мантиссы и 24-разрядную мантиссу в прямом коде с точкой, расположенной слева (разряд НВ отсутствует). Мантиссу можно рассматривать как шесть 4-разрядных шестнадцатеричных цифр'; для нормализованного числа первая шестнадцатеричная цифра должна быть отлична от нуля.
Операции с плавающей точкой
Теперь, когда мы ознакомились с некоторыми форматами, можно попытаться произвести вычисления над числами с плавающей точкой. Минимальный набор операций с плавающей
точкой включает сложение, вычитание, умножение, деление и сравнение чисел с плавающей точкой, а также преобразования целых чисел из формата с плавающей точкой в формат с фиксированной точкой и обратно.
Точные правила выполнения указанных выше операций зависят от конкретного формата с плавающей точкой, однако имеются некоторые основные шаги, являющиеся общими для всех форматов. Заметим, что сдвиг влево двоичной точки в мантиссе соответствует делению на 2 и, следовательно, может быть скомпенсирован путем увеличения порядка на 1. Подобно этому, сдвиг двоичной точки вправо компенсируется уменьшением порядка на 1. Тогда
1101.001- 2° =.1ЮЮОЬ 24 = 1101001.. 2-э
Преобразование числа из формата с фиксированной точкой в формат с плавающей точкой можно выполнить следующим образом:
Преобразовать число в систему, используемую для представления мантиссы (например, в прямой код).
Начиная с порядка, равного нулю, выполнить сдвиг двоичной точки мантиссы вправо или влево, соответственно корректируя порядок, пока мантисса нс будет нормализована.
Преобразовать полученный порядок в соответствующую систему представления (например, в систему со смещением 127).
Упаковать знак мантиссы, нормализованную мантиссу и порядок в формат с плавающей точкой.
Для того чтобы преобразовать число из формата с плавающей точкой в формат с фиксированной точкой, необходимо просто произвести действия в соответствии с формулой, которая определяет формат с плавающей точкой, выполнив при этом все необходимые преобразования чисел из одной системы представления в другую. Иногда мы будем обнаруживать условие «переполнения» — это происходит, когда значение числа с плавающей точкой оказывается слишком большим для формата с фиксированной точкой. В других случаях могут встретиться числа с плавающей точкой, нс имеющие точного представления в предписанном формате с фиксированной точкой. Во всех этих ситуациях необходимо использовать приблизительные величины, получаемые путем отбрасывания цифр или округления.
Алгоритм сложения и вычитания чисел с плавающей точкой состоит из трех шагов:
Производится «выравнивание» позиционных точек чисел с тем, чтобы оба числа имели один и тот же порядок. Это выполняется путем дснормализации меньшего операнда так, чтобы порядки обоих операндов совпадали.
Производится сложение или вычитание мантисс.
Результат нормализуется. Этот шаг необходим, так как при сложении мантисс результат может получиться больше чем 1, а при их вычитании — значительно меньше чем 0,5 (предполагается использование формата ЭВМ. PDP-11).
Для умножения и деления чисел с плавающей точкой выравнивания позиционных точек производить не требуется, так как умножение или деление масштабных коэффициентов соответствует сложению или вычитанию порядков. Умножение и деление выполняются следующим образом:
Производится сложение порядков (при умножении) или их вычитание (при делении).
Осуществляется умножение или деление мантисс.
Результат нормализуется.
Для выполнения операций с плавающей точкой на ЭВМ возможны два подхода. Некоторые вычислительные системы располагают встроенными аппаратными средствами и командами, позволяющими обрабатывать числа с плавающей точкой в предписанном формате. Независимо от того, имеются ли такие аппаратные средства, всегда можно написать программы, которые обрабатывают числа в любом требуемом формате. Однако на одной и той же машине выполнение операций с плавающей точкой аппаратными средствами производится в 10—100 раз быстрее, чем программным способом. Поэтому в тех случаях, когда часто используются операции с плавающей точкой, аппаратный подход является более предпочтительным.
4.12. КОДИРОВАНИЕ СИМВОЛОВ
В гл. 1 было указано, что последовательность битов (двоичных цифр) не обязательно служит для представления числа и что входные и выходные данные современных ЭВМ в большинстве своем являются нечисловыми. К распространенному типу нечисловых данных относится текст, т. е. строки символов определенного набора. В ЭВМ каждый символ представляется последовательностью (комбинацией) битов в соответствии с установленной договоренностью.
Наиболее широко для представления символов используется ■Стандартный американский код для обмена информацией ASCII. В этом коде каждый символ представляется 7-битовой последовательностью двоичных цифр, а всего может быть пред- -ставлено 128 различных символов (см. приложение А). Таким образом, строка текста Yeccchl представляется списком из семи 7-битовых чисел:
1011001 1100101 1100011 1100011 1100011 1101000 0100001
Некоторые 7-битовые последовательности кода ASCII служат не для представления символов, а для обозначения функций управления устройствами. Например, последовательность CR (0001101) используется для возврата головки печатающего устройства (или курсора) к первой колонке, а последовательность LF (0001010)—для перевода строки. Большинство других символов управления предназначено для использования каналами передачи данных, однако следует помнить, что в различных, вычислительных системах эти символы могут иметь разное назначение.
Большинство ЭВМ оперирует 8-битовой величиной как одним элементом — байтом, при этом каждый байт служит для представления одного символа. Назначение «лишнего» бита при использовании 7-битового кода ASCII зависит от конкретной системы или программы. Иногда этому биту присваивается определенное значение, в некоторых случаях он игнорируется, тогда как в других используется для кодирования дополнительных 128 символов, не представленных в коде ASCII.
Важной особенностью кода ASCII является то, что комбинации двоичных цифр, соответствующие буквам и цифрам, образуют числовые последовательности, несущие смысловую нагрузку, так что сортировку строк текста можно производить с помощью машинных команд, предназначенных для сравнения числовых значений. Таблица кода ASCII позволяет также объяснить, почему в телефонном справочнике аббревиатуры HFC, NCR и TWA расположены перед Haag, Naar и Taaffe соответственно.
УПРАЖНЕНИЯ
Каким образом, располагая только пятью свечами, можно украсить торт при праздновании 31-летия?
Как можно объяснить, что на торте, испеченном к вашему дню рождения, зажжено только три свечи, если вам исполняется 3, 7, 13, 21, 31, 43, 57, 73 года?
Выполните следующие преобразования:
11010112 = ?10 б) 1740038 = ?10 в) 101101112 = ?10г) 67.248 = ?10
10100.1Ю12 = ?10е) F3A5ie = ?10
120103 = ?10з) AB3Die = ?10
71568 = ?юк) 15С.381в = ?10
Выполните следующие преобразования:
а) в) Д) ж)и)а) в) Д) ж)и)11010112 = ?1в 10110111а = ?1610100.11012 = ?1в 110110012 = ?8101111.01112 = ?811010112 = ?1в 10110111а = ?1610100.11012 = ?1в 110110012 = ?8101111.01112 = ?8б) 1740038 = ?2 г) 67.248 = ?2 е) F3A516 = ?2 з) AB3Dle = ?2к) 15С.381в = ?3б) 1740038 = ?2 г) 67.248 = ?2 е) F3A516 = ?2 з) AB3Dle = ?2к) 15С.381в = ?3
а) в) Д) ж)
и)
а) в) Д) ж)
и)
11010112 = ?1в 10110111а = ?16
10100.11012 = ?1в 110110012 = ?8
101111.01112 = ?8
11010112 = ?1в 10110111а = ?16
10100.11012 = ?1в 110110012 = ?8
101111.01112 = ?8
б) 1740038 = ?2 г) 67.248 = ?2 е) F3A516 = ?2 з) AB3Dle = ?2
к) 15С.381в = ?3
б) 1740038 = ?2 г) 67.248 = ?2 е) F3A516 = ?2 з) AB3Dle = ?2
к) 15С.381в = ?3
Выполните следующие преобразования:
12510 = ?3б)348910 = ?8-
2О91о = ?2г)971410 = ?8
Д)13210 = ?2е)2385110 = ?16
72710 = ?5з)5719010 = ?16
143510 = ?8 к) 6511310 = ?1в
4.6. Выполните сложение следующих пар двоичных чисел и укажите все переносы:
а) 110101
б) 101110
в) 11011101
г) 1110010
+ 11001
+ 100101
+ 1100011
+ 1101101
Выполните вычитание чисел, которые использовались в предыдущем упражнении, и укажите все заемы.
Выполните сложение следующих пар восьмеричных чисел:
а) 1372 б) ,47135
+ 4631+ 5125
в) 175214 г) 110321
+ 152405+ 56573
4.9. Выполните сложение следующих пар шестнадцатеричных чисел:
а) .1372 б) 4F1A5 в) F35B г) 1B90F
+ 4631+ B8D5 +27Е6+ С44Е
Получите 8-разрядное представление в прямом, дополнительном и обратном кодах каждого из следующих десятичных чисел:
+18, +115, +79, —49, —3, —100
Предположим, что мы имеем 4п-разрядное двоичное число В и соответствующее ему n-разрядное шестнадцатеричное число Н. Докажите, что дополнение числа В до двух соответствует дополнению числа Н до 16. Сформулируйте подобное утверждение, которое будет справедливо для восьмеричного представления двоичного числа.
Выполните предыдущее упражнение, используя дополнение числа В до единицы и дополнение числа Н до 15.
Укажите наиболее вероятные значения остатков на текущем счете, если они записаны следующим образом: $ 9871.23, $ 9905.74, $ 123.46. Для каждого из этих случаев покажите, каким будет новый остаток, если сделать вклад 102.99 долл.?
Если мы имеем целое число х, где —г^Сх^»-1—1, то двоичное представление числа х в дополнительном коде [х], записанное в виде положительного числа, можно определить следующим образом: [х]=х при х^0 и [х]=2п—|х| при х<0, где |х|—абсолютное значение х. Пусть у— другое целое число из того же диапазона, что и х. Докажите, что правила сложения чисел в дополнительном коде (разд.4.6) являются правильными, убедившись в том, что всегда справедливо приведенное ниже уравнение:
[^ + У] = И + М по модулю 2П
(Указания. Рассмотрите четыре случая для разных комбинаций знаков х и у. Можно считать, что |х|^|г/|.)
Выполните предыдущее упражнение, используя соответствующие выражения и правила для сложения двоичных чисел в обратном коде.
Укажите, имеет ли место переполнение при сложении следующих 8-раз- рядных чисел, представленных в дополнительном коде:
г)г)00100110010110100010011001011010а) 11010100 б) 10111001 в) 01011101
г)
г)
00100110
01011010
00100110
01011010
+ 10101011+ 11010110+ 00100001
Дало m-разрядное двоичное число X, представленное в дополнительном коде. Докажите, что л-разрядное представление числа X в дополнительном коде, где л>т, может быть получено путем присоединения к т-разрядному представлению числа X слева п—ш копий его знакового разряда.
Покажите, как можно реализовать функцию Bls расширенной версии языка Паскаль, используя в качестве простейших функций В add и Всот.
Пусть X и У являются n-разрядными двоичными числами, а У есть дополнение числа У до единицы. Докажите, что при выполнении операции X—* У заем из старшего разряда производится только в том случае, если операция Х+У+1 не приводит к возникновению переноса из старшего разряда.
Докажите, что двоичное число, представленное в дополнительном коде,. мЬжно умножить на 2 путем его сдвига на один разряд влево, установив перенос в младший разряд равным нулю и не обращая внимания на какой- либо перенос из старшего разряда. Считайте, что переполнения не происходит. Сформулируйте правило обнаружения переполнения.
Сформулируйте и обоснуйте метод умножения двоичного числа в об* ратном коде на два, подобный методу, предложенному выше.
Сформулируйте правило деления двоичного числа, представленного в дополнительном коде, на два, которое выполняется при помощи операции сдвига вправо. Приведите оператор языка Паскаль, который позволяет точна определить целое частное, получаемое в тех случаях, когда делимое не является кратным числу 2. Используйте только положительные аргументы с функциями DIV и MOD, так как стандартный язык Паскаль допускает изменение результатов выполнения этих функций с отрицательными аргументами в различных применениях.
Выполните предыдущее упражнение для чисел в обратном коде.
Перепишите программу умножения чисел без знака, приведенную на рис. 4.3, так, чтобы тру и loProd являлись одной и той же переменной.
Напишите программу на расширенной версии языка Паскаль, которая обеспечивает обработку битов множителя слева направо, а не справа налево, как это сделано в программе на рис. 4.3.
Перепишите программу деления чисел без знака, приведенную на рис, 4.7, таким образом, чтобы quot и loDvnd являлись одной переменной.
Покажите, как производится вычитание чисел в двоично-десятичной системе; сформулируйте правила возникновения заемов и использования корректирующего коэффициента. Покажите, как применяются правила на следующих примерах вычитания: 9—3, 5—7, 4—9, 1—8.
Сколько различных ненормализованных (значение разряда НВ равно нулю) представлений имеет число +1.0 в формате с плавающей точкой мини-ЭВМ PDP-11?
Какое число является наименьшим положительным нормализованным числом в формате с плавающей точкой мини-ЭВМ PDP-11? Укажите также наибольшее число.
Покажите, как в формате с плавающей точкой мини-ЭВМ PDP-11 еле* довало бы записать в памяти вычислительной машины следующие числа: 17, 40, 9.25, 15/64, 7/1024.
Докажите, что если два числа X и У в формате с плавающей точкой мини-ЭВМ PDP-11 являются нормализованными, то X должно быть больше У, если порядок числа X больше порядка числа У. (Указание. Рассмотрите значения порядков и мантисс для наихудшего случая.)
Почему в телефонном справочнике HFC, NCR и TWA расположены пе* ред Haag, Naar и Taaffe соответственно?
ЛИТЕРАТУРА
Тщательное и полное изложение всех вопросов, рассмотренных в данной главе, можно найти в книге Кнута (Knuth D., Seminumerical Algorithms, Addison-Wesley, 1969. [Имеется перевод: Кнут Д. Искусство программирования для ЭВМ. Т. 1. Основные алгоритмы. — М.: Мир, 1976.]). В ней прекрасно проведен анализ свойств систем счисления. Интерес читателей вызовут также исторические сведения и размышления автора, которые делают изложение более живым.
Аппаратным средствам, предназначенным для выполнения арифметических операций, а также свойствам различных систем счисления посвящен труд Хвен- га (Hwang К., Computer Arithmethic, Wiley, 1979). В книге Шмида (Schmid Н., Decimal Arithmetic, Wiley, 1974) содержится полное описание методов выполнения операций над двоично-десятичными числами.
55
5
5
БАЗОВАЯ СТРУКТУРА (ОРГАНИЗАЦИЯ) ЭВМ
Типичная ЭВМ состоит из центрального процессора, памяти и устройств ввода-вывода. Со времени появления в 40-х годах первых электронных вычислительных машин технология производства каждой из этих трех подсистем была значительно усовершенствована. За последнее десятилетие благодаря развитию интегральной технологии особенно существенно были улучшены характеристики центральных процессоров и запоминающих устройств (памяти); кроме того, была снижена их стоимость. В настоящее время по цене, нс превышающей стоимости качественной стереофонической системы, можно приобрести в личное пользование достаточно мощную ЭВМ, за которую в 70-х годах потребовалось бы заплатить больше, чем за дом с четырьмя спальнями.
Несмотря на успехи, достигнутые в области технологии, существенных изменений в базовой структуре и принципах работы вычислительных машин нс произошло. Так, еще в 1946 г. в описании впервые предложенной ЭВМ с хранимой в памяти программой Беркс, Голдстайн и Нейман отметили1):
«Мы располагаем... двумя различными типами памяти: памятью чисел и памятью команд. Тем не менее если команды машине представлены в виде числового кода и если машина каким-либо образом в состоянии отличать числа от команд, то блок памяти можно использовать для хранения и тех и других».
И в настоящее время почти во всех вычислительных машинах для хранения данных (чисел) и команд используется одна и та же память. Редким исключением в этом отношении является однокристальная микро-ЭВМ MCS-48 (ее описание приведено в гл. 19). Первые вычислительные машины обладали и многими другими качествами, характерными для современных ЭВМ.
° Computer Structures: Readings and Examples, by C. G. Bell, A. Newell, McGraw-Hill, 1971.
В настоящей главе приведено описание базовой структуры и основных особенностей центральных процессоров и запоминающих устройств ЭВМ. Рассмотрены три наиболее распространенных типа центральных процессоров — с аккумуляторами, с регистрами общего назначения и ориентированные на использование памяти со стековой организацией. Работа центральных процессоров показана на примере гипотетических машин, обладающих характеристиками и набором команд реальных ЭВМ. В гл. 7 и 8 приведено подробное описание различных способов адресации, а также типов операций, выполняемых процессорами. Структура центральных процессоров, применяемых на практике, описана в части III настоящей книги.
Следует отмстить, что в книге рассмотрены не все особенности и возможные варианты архитектуры ЭВМ. К настоящему времени предложено несколько структур вычислительных машин, существенно отличающихся от традиционных, причем многие из них реализованы. К ним следует отнести машины, память которых представляет собой стек, а нс матричное ЗУ, машины, обрабатывающие потоки данных, а не потоки команд; машины, центральные процессоры которых вместо того, чтобы считывать из памяти операнды, посылают в нее команды; машины, в которых алгоритмы вычисления результата определены как функциональные зависимости, а не последовательности строго определенных элементарных шагов. Существует также множество вычислительных систем, имеющих в своем составе более одного процессора. Тем не менее рассмотренные в книге понятия применимы к подавляющему большинству ЭВМ, выпускаемых промышленностью и используемых на практике в настоящее время.
ПАМЯТЬ
Память вычислительной машины представляет собой матрицу, состоящую из строк двоичных разрядов (битов) с произвольным доступом, каждой из* которых присвоен свой адрес. Если наименьшая длина адресуемой строки равна 1 байт, то память имеет побайтовую адресацию; если же такой строкой является слово, то это память с пословной адресацией. В большинстве современных ЭВМ используются запоминающие устройства с адресацией первого типа.
Организация памяти
На рис. 5.1, а показана память емкостью 216 байт. Каждый байт такой памяти однозначно определяется с помощью 16-раз- рядного адреса. В дальнейшем память машины всегда будет
изображаться таким образом, чтобы строки с меньшими адресами находились сверху. Разряды в слове или байте нумеруются справа налево, начиная с нуля. При этом номера разрядов соответствуют показателям их числовых весов без знака, записанных в виде степеней числа 2. (Например, разряд 5 имеет вес 25 = 32.) Данному условию удовлетворяют почти все мини- и
Рис. 5.1. Организация памяти:Рис. 5.1. Организация памяти:
Рис. 5.1. Организация памяти:
Рис. 5.1. Организация памяти:
а — память с побайтовой адресацией для 8-разрядного процессора; б — память с побайтовой адресацией для 16-разрядного процессора; в — память с побайтовой адресацией для 16-разрядного процессора, имеющая другую нумерацию байтов.
микро-ЭВМ, а из ЭВМ, рассматриваемых в настоящей книге, — все машины, за исключением процессора 9900 фирмы Texas Instruments. (Нумерация разрядов в машинах семейства IBM 370 производится в обратном порядке; это не является общепринятым.
Вычислительная машина может располагать памятью с побайтовой адресацией даже в том случае, если ее шина включает 16 и более разрядов. Например, на рис. 5.1,6 показана организация памяти мини-ЭВМ PDP-11. Каждое 16-разрядное слово этой памяти состоит из двух последовательно расположенных байтов, первый из которых имеет четный адрес (граница слова). Например, байты 2 и 3 образуют одно полное слово, а байты 3 и 4 нет. В командах, предназначенных для выполнения операций над словами, должны задаваться четные адреса.
Если же операции производятся над байтами, то адреса могут быть любыми.
Исходя из теории чисел, естественным представляется размещение байтов, показанное на рис. 5.1,6, так как здесь младшие разряды слова содержатся в байте, имеющем меньший номер. На рис. 5.1, в приведен другой способ адресации байтов в 16-разрядной машине, в соответствии с которым байты с меньшими адресами расположены слева. Хотя уравнения, связывающие номера разрядов и байтов, в этом случае являются менее изящными, такое размещение позволяет легче разрешить ряд практических проблем по хранению символов, представленных с помощью восьми двоичных цифр, в последовательных байтах. На рис. 5.1, б и в показано, что последовательность символов STRING записана в байтах с последовательными адресами, начиная с байта с адресом 6. Очевидно, что расположение символов на рис. 5.1, в более удобно для чтения. Кроме того, при таком расположении упрощается лексикографическая обработка строк символов, производимая с помощью команд, оперирующих словами (см. упр. 5.3). Во всех вычислительных машинах, рассматриваемых в данной книге, кроме ЭВМ. 8086 и PDP-11, используется способ адресации, показанный на рис. 5.1, в.
Моделирование работы центральных процессоров и основной памяти
Моделирование работы центрального процессора и основной памяти вычислительной машины можно осуществить на другой, более мощной ЭВМ, используя программу, написанную на языке Паскаль. Так как основная память моделируемой машины представляет собой массив, то ее можно определить с помощью описаний языка Паскаль, как это сделано ниже для памяти, приведенной на рис. 5.1, а.
TYPE
byte= ARRAY[7::0] OF bit;
address=0..65535;
VAR
MEM : ARRAY [address] OF byte;'
Дополнительные средства языка Паскаль, обеспечивающие описание организации ЭВМ, определены в приложении Б. В большинстве случаев читатель, знакомый со стандартным языком Паскаль, имеет представление о возможностях указанных средств. В приведенном выше примере во второй строке сверху определяется байт (byte) как массив, состоящий из 8 бит (bit), пронумерованных слева направо от 7 до 0, где bit является предварительно определенным типом переменной,
имеющей значения 0 и 1. В следующей строке устанавливаете^, что действительный адрес (address) есть число в пределах от О до 65 535. В последней строке определяется память, т. е. массив, состоящий из байтов, каждый из которых имеет свой адрес. Так как общее число байтов памяти составляет 216, то адрес можно задать с помощью 16-разрядного целого числа без знака.
Типы запоминающих устройств
В качестве основной памяти вычислительных машин используются различные типы запоминающих устройств (ЗУ). Запоминающие устройства с возможностью считывания и записи позволяют записывать в них данные по любому адресу, а также считывать эти данные в любой момент времени. Постоянные запоминающие устройства (ПЗУ) допускают считывание содержимого ячеек памяти по любому адресу. Однако данные в эти устройства могут записываться только один раз в процессе их изготовления.
Программируемые постоянные запоминающие устройства (ППЗУ) сходны с ПЗУ и отличаются лишь тем, что с помощью программирующего устройства пользователь может сам записать в них данные (т. е. запрограммировать ППЗУ). Кристаллы ППЗУ выпускаются в таком виде, что все биты памяти имеют определенное значение, например 1. Программирующее устройство служит для установки определенных битов в нуль, что обычно производится путем пережигания плавких перемычек, соответствующих каждому биту ПЗУ.
Программируемые постоянные запоминающие устройства с возможностью стирания (СППЗУ) подобны ППЗУ, за исключением того, что под действием ультрафиолетового излучения все биты такого ЗУ могут быть вновь установлены в 1. Программирующее устройство воздействует на электроны таким образом, что они попадают в ловушки, каждая из которых соответствует нулевому значению бита, и остаются там до тех пор, пока не будут удалены с помощью ультрафиолетового излучения.
Любая вычислительная машина имеет в своем составе ЗУ с возможностью считывания и записи, которое служит для хранения изменяющихся данных. Программы в зависимости от типа ЭВМ и конкретных применений также могут быть записаны в такое ЗУ или же для их хранения могут использоваться ПЗУ, ППЗУ или СППЗУ.
Все рассмотренные выше запоминающие устройства представляют собой ЗУ с произвольной выборкой (ЗУПВ), так как каждое из них имеет одинаковое время доступа ко всем ячейкам. Однако терминология, связанная с вычислительными машинами, сложилась таким образом, что название ЗУПВ чаще
всего используют по отношению к памяти, допускающей считывание и запись. Для того чтобы быть корректным, для ЗУ с возможностью считывания и записи следовало бы использовать другое обозначение, например ЗУСЗ.
ПРОЦЕССОРЫ С АККУМУЛЯТОРАМИ
Процессор, имеющий простейшую организацию, содержит один или два регистра, называемых аккумуляторами, которые используются при выполнении арифметических и логических операций и передаче данных. Так, MCS-48 фирмы Intel является процессором с одним аккумулятором, а ЦП 6809 фирмы Motorola представляет собой процессор с двумя аккумуляторами. Процессоры, в которых арифметические и логические операции выполняются с участием более двух регистров, классифицируются как процессоры с регистрами общего назначения (РОН) и будут рассмотрены ниже.
Организация процессора с одним аккумулятором
На рис. 5.2 показана внутренняя организация процессора с одним аккумулятором. Наборы регистров и команд этого гипотетического (hypothetical) процессора те же, что у процессора 6809 фирмы Motorola (см. гл. 17), поэтому назовем его устройством Н6809.
Процессор Н6809 позволяет производить обращение к памяти емкостью 216 (65 536) байт, имеющей организацию, показанную на рис. 5.1,о; длина адреса составляет 2 байт. Большинство команд обеспечивает выполнение операций над 1-байтовыми величинами, а некоторые — над 2-байтовыми. Команды процессоров Н6809 занимают 1, 2 или 3 байт памяти. В состав процессора входит несколько регистров и функциональных узлов, краткое описание которых приведено ниже.
Регистр команд (IR)— 8-разрядный регистр, в котором хранится первый байт выполняемой команды.
Регистр исполнительного адреса (EAR) — 16-разрядный регистр, содержащий адрес памяти, по которому во время выполнения команды процессор производит считывание из памяти или запись в память.
Счетчик команд (PC) — 16-разрядный регистр, где находится адрес следующей подлежащей выполнению команды.
Аккумулятор (А) — 8-разрядный регистр, в котором размещены данные, подлежащие обработке.
Индексный регистр (X) — 16-разрядный регистр, содержащий адрес или 16-битовые данные, которые используются программой.
Указатель стека (SP) — 16-разрядный регистр, содержащий адрес верхушки стека адресов возврата, организованного в памяти.
Рис. 5.2. Организация процессора с одним аккумулятором (Н6809).
Регистр нуля (Z) — 1-разрядный регистр (триггер), который при выполнении любой команды, оперирующей данными, устанавливается процессором в 1, если команда приводит к получе-
Рис. 5.3. Набор регистров процессора Н6809, используемый для программирования.
нию нулевого результата, или же в 0, если результат отличен от нуля.
Арифметическо-логическое устройство (АЛУ) — производит операции над двумя 8-разрядными величинами с целью получения 8-разрядного результата.
Устройство управления — дешифрирует команды и управляет различными узлами с целью осуществления выборки и выполнения команд.
Интерфейс памяти и ввода-вывода — в соответствии с сигналами, поступающими от устройства управления, производит обращение к памяти (запись и считывание) и осуществляет связь с устройствами ввода-вывода.
Несмотря на то что для работы процессора Н6809 важны все рассмотренные выше узлы, команды непосредственно оперируют только регистрами PC, А, X, SP и Z, содержимое которых имеет существенное значение после выполнения каждой команды. Указанные регистры определяют состояние процессора и используются при программировании (рис. 5.3). Следует отметить, что только эти регистры доступны программисту.
Основной машинный цикл
Работа процессора Н6809 (а также почти всех других процессоров) состоит из двух непрерывно повторяющихся шагов: считывания очередной команды из памяти (цикл выборки) и
PROGRAM Н6809 (input,output);
TYPE byte = ARRAY [7::0] OF bit;
word = ARRAY [15;:0] OF bit;
VAR MEM : ARRAY [0.,65535] OF byte;
EAR, PC, X, SP : word;
IR, A ; byte; Z : bit;
PROCEDURE Fetch;—
BEGIN..
IR :^ MEM(PC); {Read next instructibn.) *
PC : = PC + 1; {Bump PC to next instruction#j
END;-'
PROCEDURE Execute;
BEGIN {Will be defined later.} END;
BEGIN,-
PC := 0; ,{Start at PC=0 on cold start.)
WHILE true DO
BEGIN
Fetch;
Execute;
END;
END.
Рис. 5.4. Основной машинный цикл процессора Н6809.
выполнения предписываемых этой командой операций (исполнительный цикл). Этот основной машинный цикл можно описать на языке Паскаль (рис. 5.4). Во время цикла выборки осу-
ществляется считывание первого байта той команды, на которую указывает счетчик команд PC, и производится приращение содержимого PC для того, чтобы он указывал на следующий байт в памяти. Операции, выполняемые во время исполнительного цикла, зависят от вида команды и могут включать считывание дополнительных байтов команды и соответствующие изменения содержимого счетчика PC. Использование обычного счетчика в качестве счетчика, команд не является случайным. Как писал Нейман, «очевидно, что необходимо иметь возможность получать числа из любой области памяти в произвольные моменты времени. Однако, если мы имеем дело с командами, обработка может быть более систематической, так как по крайней мере часть команд управления можно расположить в виде линейной последовательности. Поэтому процесс управления можно организовать таким образом, что для получения следующей команды переход будет осуществляться от n-й ячейки памяти к (п-|-1)-й».
Машинные команды
Длина машинных команд гипотетического процессора Н6809 составляет 1—3 байт. Первый байт любой команды, называемый кодом операции, однозначно задает операцию, которую необходимо выполнить. Другие байты определяют операнд и представляют собой либо 8-разрядное значение данных, либо 16-разрядный адрес, используемый при выполнении команды.
В табл. 5.1 приведены 29 машинных команд процессора Н6809. С помощью 8-разрядного кода операций можно было бы задать в общей сложности 256 команд, однако в действительности для определения команд реального процессора 6809 используется только 223 8-разрядных кодов операций. Каждому коду операции соответствует свое буквенное обозначение — мнемоника, которая используется для распознавания команд. В некоторых случаях одно и то же мнемоническое обозначение имеют два или три кода операции (например, обозначение LDA соответствует трем кодам операции), поэтому для того, чтобы отличать их, перед операндом помещается специальный символ (# или @).
По содержимому ячеек памяти невозможно отличить команды от данных. Например, байт 4Fie может представлять либо код операции CLRA, либо число 79ю. Только процессор в состоянии их распознать. Во время цикла выборки процессор интерпретирует байты памяти как команды, а во время исполнительного цикла — как данные. Других способов проверки не существует. Если в результате какой-либо ошибки счетчик команд укажет на область памяти, в которой хранятся данные,
Мнемоническое обозначение
Операнд
Длина команды, байт
Код операции (шестнадцати
Описание операции
NOP112CLRA14FСОМА143NEGA140LDA# data286LDA@X1A6LDAaddr3B6STA@x1A7STAaddr3B7ADDA# data28BADDAaddr3BBANDA# data284ANDAaddr3B4СМРА# data281СМРАaddr3BlLDX# addr38ELDXaddr3BESTXaddr3BFCMPX# addr38CCMPXaddr3BCADDX# addr330ADDXaddr331LDS# addr38FBNEoffset226BEQoffset227BRAoffset220JMPaddr37ENOP112CLRA14FСОМА143NEGA140LDA# data286LDA@X1A6LDAaddr3B6STA@x1A7STAaddr3B7ADDA# data28BADDAaddr3BBANDA# data284ANDAaddr3B4СМРА# data281СМРАaddr3BlLDX# addr38ELDXaddr3BESTXaddr3BFCMPX# addr38CCMPXaddr3BCADDX# addr330ADDXaddr331LDS# addr38FBNEoffset226BEQoffset227BRAoffset220JMPaddr37EНет операцииОчистка аккумулятораФормирование обратного кода содержимого аккумулятораФормирование дополнительного кода содержимого аккумулятораЗагрузка непосредственных данных data в аккумуляторЗагрузка МЕМ[Х] в аккумуляторЗагрузка MEM [ad dr] в аккумуляторЗапись содержимого аккумулятора в МЕМ[Х]Запись содержимого аккумулятора в MEM[addr]Прибавление непосредственных данных data к содержимому аккумулятораПрибавление MEM [addr] к содержимому аккумулятораЛогическая операция И над непосредственным операндом и содержимым аккумулятораЛогическая операция И над MEM [addr] и содержимым аккумулятораУстановка Z в соответствии с результатом операции А — dataУстановка Z в соответствии с результатом операции A—MEM[addr]Загрузка адреса addr в регистр XЗагрузка MEMW[addr] в регистр XЗапись содержимого регистра X в MEMW[addr]Установка Z в соответствии с результатом операции X — addrУстановка Z в соответствии с результатом операции л— MEMWfaddr]Прибавление addr к содержимому регистра XПрибавление MEMW[addr] к содержимому регистра XЗагрузка addr в регистр SPПереход, если результат не равен нулю (Z=0)Переход, если результат равен нулю (Z=l)Безусловный переходПереход по адресу addrНет операцииОчистка аккумулятораФормирование обратного кода содержимого аккумулятораФормирование дополнительного кода содержимого аккумулятораЗагрузка непосредственных данных data в аккумуляторЗагрузка МЕМ[Х] в аккумуляторЗагрузка MEM [ad dr] в аккумуляторЗапись содержимого аккумулятора в МЕМ[Х]Запись содержимого аккумулятора в MEM[addr]Прибавление непосредственных данных data к содержимому аккумулятораПрибавление MEM [addr] к содержимому аккумулятораЛогическая операция И над непосредственным операндом и содержимым аккумулятораЛогическая операция И над MEM [addr] и содержимым аккумулятораУстановка Z в соответствии с результатом операции А — dataУстановка Z в соответствии с результатом операции A—MEM[addr]Загрузка адреса addr в регистр XЗагрузка MEMW[addr] в регистр XЗапись содержимого регистра X в MEMW[addr]Установка Z в соответствии с результатом операции X — addrУстановка Z в соответствии с результатом операции л— MEMWfaddr]Прибавление addr к содержимому регистра XПрибавление MEMW[addr] к содержимому регистра XЗагрузка addr в регистр SPПереход, если результат не равен нулю (Z=0)Переход, если результат равен нулю (Z=l)Безусловный переходПереход по адресу addr
NOP
1
12
CLRA
1
4F
СОМА
1
43
NEGA
1
40
LDA
# data
2
86
LDA
@X
1
A6
LDA
addr
3
B6
STA
@x
1
A7
STA
addr
3
B7
ADDA
# data
2
8B
ADDA
addr
3
BB
ANDA
# data
2
84
ANDA
addr
3
B4
СМРА
# data
2
81
СМРА
addr
3
Bl
LDX
# addr
3
8E
LDX
addr
3
BE
STX
addr
3
BF
CMPX
# addr
3
8C
CMPX
addr
3
BC
ADDX
# addr
3
30
ADDX
addr
3
31
LDS
# addr
3
8F
BNE
offset
2
26
BEQ
offset
2
27
BRA
offset
2
20
JMP
addr
3
7E
NOP
1
12
CLRA
1
4F
СОМА
1
43
NEGA
1
40
LDA
# data
2
86
LDA
@X
1
A6
LDA
addr
3
B6
STA
@x
1
A7
STA
addr
3
B7
ADDA
# data
2
8B
ADDA
addr
3
BB
ANDA
# data
2
84
ANDA
addr
3
B4
СМРА
# data
2
81
СМРА
addr
3
Bl
LDX
# addr
3
8E
LDX
addr
3
BE
STX
addr
3
BF
CMPX
# addr
3
8C
CMPX
addr
3
BC
ADDX
# addr
3
30
ADDX
addr
3
31
LDS
# addr
3
8F
BNE
offset
2
26
BEQ
offset
2
27
BRA
offset
2
20
JMP
addr
3
7E
Нет операции
Очистка аккумулятора
Формирование обратного кода содержимого аккумулятора
Формирование дополнительного кода содержимого аккумулятора
Загрузка непосредственных данных data в аккумулятор
Загрузка МЕМ[Х] в аккумулятор
Загрузка MEM [ad dr] в аккумулятор
Запись содержимого аккумулятора в МЕМ[Х]
Запись содержимого аккумулятора в MEM[addr]
Прибавление непосредственных данных data к содержимому аккумулятора
Прибавление MEM [addr] к содержимому аккумулятора
Логическая операция И над непосредственным операндом и содержимым аккумулятора
Логическая операция И над MEM [addr] и содержимым аккумулятора
Установка Z в соответствии с результатом операции А — data
Установка Z в соответствии с результатом операции A—MEM[addr]
Загрузка адреса addr в регистр X
Загрузка MEMW[addr] в регистр X
Запись содержимого регистра X в MEMW[addr]
Установка Z в соответствии с результатом операции X — addr
Установка Z в соответствии с результатом операции л— MEMWfaddr]
Прибавление addr к содержимому регистра X
Прибавление MEMW[addr] к содержимому регистра X
Загрузка addr в регистр SP
Переход, если результат не равен нулю (Z=0)
Переход, если результат равен нулю (Z=l)
Безусловный переход
Переход по адресу addr
Нет операции
Очистка аккумулятора
Формирование обратного кода содержимого аккумулятора
Формирование дополнительного кода содержимого аккумулятора
Загрузка непосредственных данных data в аккумулятор
Загрузка МЕМ[Х] в аккумулятор
Загрузка MEM [ad dr] в аккумулятор
Запись содержимого аккумулятора в МЕМ[Х]
Запись содержимого аккумулятора в MEM[addr]
Прибавление непосредственных данных data к содержимому аккумулятора
Прибавление MEM [addr] к содержимому аккумулятора
Логическая операция И над непосредственным операндом и содержимым аккумулятора
Логическая операция И над MEM [addr] и содержимым аккумулятора
Установка Z в соответствии с результатом операции А — data
Установка Z в соответствии с результатом операции A—MEM[addr]
Загрузка адреса addr в регистр X
Загрузка MEMW[addr] в регистр X
Запись содержимого регистра X в MEMW[addr]
Установка Z в соответствии с результатом операции X — addr
Установка Z в соответствии с результатом операции л— MEMWfaddr]
Прибавление addr к содержимому регистра X
Прибавление MEMW[addr] к содержимому регистра X
Загрузка addr в регистр SP
Переход, если результат не равен нулю (Z=0)
Переход, если результат равен нулю (Z=l)
Безусловный переход
Переход по адресу addr
Продолжение табл. 5.1
Мнемоническое обозначение
Операнд
Длина команды, байт
Код операции (шест- надцат.)
Описание операции
JSR
addr
3
BD
Переход к подпрограмме по адресу addr
Возврат из подпрограммы
RTS
1
39
Примечания: data — 8-разрядное значение данных; addr — 16-разрядный адрес памяти (2 байт); offset — 8-разрядное целое число со знаком, которое в случае перехода прибавляется к значению счетчика команд PC;
MEM[i] — байт памяти по адресу 1;
MEMW[i] —- слово памяти, начинающееся по адресу i (слово, состоящее из М£М[1] и МЕМ[14-1]).
Коды операций всех приведенных команд, за исключением ADDX, LDS, LDA, @ X и STA @Х, совпадают с кодами операций соответствующих команд реального процессора 6809 фирмы Motorola. Перечисленные здесь в качестве исключения команды реального процессора имеют длину 2 байт.
Мнемоническое обозначение команды ADDX, а также некоторые другие обозначения отличаются от тех, которые используются в языке ассемблера реального процессора 6809 (см. гл. 17).
то процессор будет упорно продолжать работу, пытаясь интерпретировать данные как последовательность команд. Если же программа будет производить запись байтов данных в ячейки памяти, занятые ее командами, то это приведет к разрушению самой программы.
Группы команд
Команды первой группы (табл. 5.1) выполняются с участием аккумулятора А. При этом наиболее часто используются команды загрузки аккумулятора и записи его содержимого в память (LDA, STA), так как для выполнения операций над данными последние необходимо помещать в аккумулятор. С помощью команды ADDA производится прибавление 8-разрядной величины к содержимому аккумулятора, в то время как команда ANDA служит для выполнения поразрядной операции И над содержимым аккумулятора и другой 8-разрядной величиной. Команда СМРА позволяет сравнивать содержимое аккумулятора А с другой величиной без модификации этих величин. Выполнение команд CLRA, СОМА и NEGA приводит соответственно к очистке аккумулятора и получению обратного и дополнительного кода его содержимого. По команде NOP никаких операций не производится; она используется при отладке программ для заполнения пустых мест, возникающих в результате устранения некоторых команд.
Во вторую группу входят команды, оперирующие регистрами X и SP. В большинстве программ регистр X используется
для хранения 16-разрядного адреса или 16-разрядных данных. Мы можем производить загрузку этого регистра, записывать его содержимое в память, осуществлять положительное или отрицательное приращение, а также сравнивать с другой 16-раз- рядной величиной. Регистр SP всегда указывает на верхушку стека адресов возврата, используемых командами вызова подпрограмм и возврата в исходную программу. Команда LDS может использоваться для начальной установки указателя стека SP в начале программы.
Команды последней группы позволяют производить условные и безусловные изменения хода выполнения программы путем записи в счетчик команды PC нового значения. С помощью (3-байтовых команд JMP и JSR можно осуществлять переходы по любому адресу, в то время как более короткие команды BNE, BEQ и BRA обеспечивают переходы или ветвления по адресам, расположенным недалеко от адреса текущей коман- ды1). Команда перехода интерпретирует свой байт offset (см. табл. 5.1) как число со знаком, представленное в дополнительном коде, значение которого находится в пределах от —128 до + 127. При выполнении перехода это число прибавляется к содержимому счетчика команд, в противном случае управление передается команде со следующим адресом. Таким образом, адрес перехода может отличаться от следующего адреса на величину в пределах от —128 до +127.
Команды могут также классифицироваться в соответствии с используемым способом адресации операндов.
Неявная адресация. Операнд однозначно задается кодом операции команды (NOP, CLRA, СОМА, NEGA, RTS).
Непосредственная адресация. Восьмиразрядный операнд содержится в байте, который следует сразу после кода операции команды (LDA*data, ADDA^data, ANDA^data, CMPA^hdata), а 16-разрядный операнд размещен в двух байтах, следующих за кодом операции (LDX^addr, CMPX#addr, ADDX*addr, LDX*addr).
Абсолютная адресация. Адрес операнда задается в двух байтах, расположенных после кода операции, причем старшим из них является первый байт (LDA addr, STA addr, ADDA addr, ANDA addr, CMPA addr, LDX addr, SIX addr, CMPX addr, ADDX addr, JMP addr, JSR addr).
Косвенная регистровая адресация. Адрес операнда содержится в индексном регистре X (LDA@X, STA@X).
Относительная адресация. Адрес операнда вычисляется как сумма содержимого счетчика команд PC и 8-разрядного
> Понятия «ветвление» и «переход» означают одно и то же. Тем не менее эти термины несут разную смысловую нагрузку и используются для отличия «абсолютных» переходов от «относительных» (см. разд. 8.6).
Программа на машинном языке
Программа на языке ассемблера
Ад
рес
Содер- Метка жимое
Код операц.
Операнд Комментарий
ORG
2A40H
Multiply MCND by MPY.
2А40
4F
START
CLRA
Set PROD to 0.
2А41
В7
STA
PROD
2А42
2С
2А43
00
2А44
Вб
LDA
MPY
Set CNT equal to MPY
2А45
2С
2А46
02
2А47
В7
STA
CNT
and do loop MPY times*
2А48
2С
2А49
01
2А4А
Вб
LOOP
LDA
CNT
Done if CNT = 0.
2А4В
2С
2А4С
01
2A4D
27
BEQ
OUT
2А4Е
10
2A4F
8В
ADDA
#-1
Else decrement CNT.
2А50
FF
2А51
В7
STA
CNT
2А52
2С
2А53
01
2А54
Вб
LDA
PROD
Add MCND to PROD*
2А55
2С
2А56
00
2А57
ВВ
ADDA .
MCND
2А58
2С
2А59
03
2А5А
В7
STA
PROD
2А5В
2С
2А5С
00
2A5D
20
BRA
LOOP
Repeat the loop again*
2А5Е
ЕВ
2A5F
Вб
OUT
LDA
PROD
Put PROD in A when done*
2А60
2С
2А61
00
2 Аб 2
7Е
JMP
1000H
Return to operating system*
2А63
10
2 Аб 4
00
Ж • е
ORG
2C00H
2С00
??
PROD
RMB
1
Storage for PROD.
2С01
??
CNT
RMB
1
Storage for CNT.
2С02
05
MPY
FCB
5
Multiplier value.
2С03
17
MCND
FCB
23
Mutliplicand value*
• •.
END
START
Рис. 5.5. Содержимое памяти, соответствующее последовательности команд и дурных.
числа в дополнительном коде, которое находится в байте, расположенном после кода операции (BNE offset, BEQ offset. BRA offset).
Подробное описание каждой команды будет дано ниже с использованием языка Паскаль.
Программа на машинном языке
На рис. 5.5 показаны величины, хранимые в памяти, которые соответствуют последовательности команд и данных, составляющих программу умножения числа 23 на число 5. Последовательность адресов, кодов операций и данных, которые находятся в двух левых колонках рис. 5.5, называется программой на машинном языке. Эта последовательность полностью определяет все операции, которые должна выполнить вычислительная машина. Процесс реализации программы будет пояснен ниже.
Язык ассемблера
Очевидно, читателю, не знакомому с программированием, содержимое двух левых колонок (рис. 5.5) мало о чем говорит. Колонки «Метка» — Label, «КОП» (Код операции) — Opcode и «Операнд» — Operand позволяют задать программу в символической форме, используя мнемонику для представления кодов операций и буквенно-цифровые метки для адресов и данных. Колонка «Комментарий» — Comments служит для пояснения действий, выполняемых программой. Эти четыре колонки составляют программу на языке ассемблера, которая с помощью другой программы, называемой ассемблером, может быть транслирована на машинный язык. Ассемблер (помимо всего прочего) вырабатывает листинг эквивалентной программы на машинном языке. Наряду с машинными командами программа на языке ассемблера содержит псевдокоманды, которые указывают ассемблеру, каким образом следует разместить в памяти программу на машинном языке. Программа, приведенная на рис. 5.5, содержит следующие четыре псевдокоманды:
ORG (Origin — Начало). Операнд псевдокоманды ORG задает адрес, по которому при загрузке программы в память должна быть размещена следующая команда. Последующие команды записываются по порядку в ячейки памяти с последовательными адресами.
RMB (Reserve Memory Bytes — Резервирование байтов , памяти). Операнд псевдокоманды RMB задает число байтов памяти, которое необходимо пропустить, не производя в них записи команд или данных. Таким образом осуществляется резер-
вирование области памяти, которая используется для хранения переменных программы.
FCB (Form Constant Byte — Формирование байтовой константы). Псевдокоманда FCB обеспечивает запись в память определенной величины длиной в 1 байт при первой загрузке программы в память, устанавливая таким образом постоянную величину, к которой можно производить обращение при выполнении программы.
END (End Assembly — Конец ассемблирования). Данная команда указывает на окончание текста, подлежащего трансляции, и задает адрес команды, которая должна быть выполнена первой.
Для всех команд языка ассемблера считается, что числовые параметры записаны в десятичной системе счисления, если после них нет символа Н0. Когда в поле метки появляется идентификатор, ему присваивается значение адреса. Например, меткам START и CNT соответствуют адреса 2А40Н и 2С01Н. Если идентификатор появляется в поле операнда, то ассемблер подставляет значение, которое было ему присвоено. Поэтому команда STA CNT эквивалентна команде STA 2С01Н.
Все описанные выше операции имеют место в процессе ассемблирования. При этом идентификатор, такой, как CNT, используется в качестве символического адреса памяти, по которому находится переменная. При прогоне, когда осуществляется выполнение программы, все действия производятся над величинами, хранимыми в памяти. Для обращения к такой величине необходимо использовать запись MEM [CNT]. Однако при рассмотрении операций, производимых при прогоне программы, идентификатор обычно используется для обозначения самой величины, которая хранится в памяти. Поэтому комментарий «Установка CNT равным МРУ» означает то же самое, что и MEM [2С01Н]: =MEM [2С02Н].
Программист, составляющий программы на языке ассемблера, должен четко представлять, чем отличаются операции, выполняемые при трансляции (ассемблировании) и прогоне программы. Поэтому поясним их отличие еще раз. Ассемблер является системной программой, осуществляющей трансляцию строк текста в последовательность байтов команд и данных, которые могут быть записаны в память машины. Ассемблер заканчивает свою работу до начала выполнения сформированной им машинной программы. До тех пор пока речь идет об ассемблере, такой идентификатор, как PROD, служит для обозначения присвоенного ему адреса (2С00Н); при выполнении программы ас-
*> Наличие символа Н (hexadecimal) указывает на то, что данный параметр записан в шестнадцатеричной системе счисления. — Прим, перев.
семблер не имеет никакого отношения к содержимому ячейки памяти по адресу 2С00Н. Использование идентификатора освобождает программиста от необходимости постоянно следить за точным адресом, по которому размещена команда или определенные данные. Несмотря на то что идентификатор служит
Адрес
Соде₽- Метка жимое
Код операц,
Операнд Комментарий
• ее
ORG
2A40H Multiply MCND .by MPY.
2А40
4FSTART
CLRA
Set PROD to 0*
2А41
В7 2С00
STA
PROD
2А44
Вб 2С02
LDA
MPYSet CNT equal to MPY
2А47
В 7 2С01
STA
CNTand do loop MPY times*
2А4А
Вб 2С01 LOOP
LDA
CNTDone if CNT = 0.
2A4D
27 10
BEQ
OUT
2A4F
8В FF
ADDA
#-1Else decrement CNT,
2А51
В 7 2С01
STA
CNT
2А54
Вб 2С00
LDA
PRODAdd .MCND to PROD. -
2А57
ВВ 2С03
ADDA
MCND’
2А5А
В7 2CQ0
STA
PROD
2A5D
20 ЕВ
BRA '
LOOP Repeat the loop again.
2A5F
Вб 2С00 OUT
LDA
PROD Put PROD in A when done.
2А62
7Е 1000
•jmp
1000H Return to operating system.
• • •
ORG
2C00H
2С00
??PROD
RMB
1Storage for PROD.
2С01.??CNT
RMB
1Storage for CNT.
2С02
05MPY
FCB
5Multiplier value.
ЧСОЗ
17MCND
FCB
23Multiplicand value. •
е е е
END
START
Рис. 5.6. Сжатый листинг программы.
для обозначения адреса, при выполнении программы программиста обычно больше интересует содержимое ячейки памяти по этому адресу. Поэтому программист использует идентификатор данной ячейки (PROD) для обращения к ее содержимому (МЕМ[2С00Н]). '
Большинство команд процессора Н6809 имеет длину более 1 байт. Поэтому представляется удобным сжать листинг программы таким образом, чтобы все байты, которые относятся к одной команде, были размещены на одной строке (рис. 5.6).
Выполнение простейшей программы '
Теперь можно рассмотреть, как выполняется программа, представленная на рис. 5.6. Данная программа обеспечивает умножение числа MCND на число MPY путем начальной установки PROD в нуль и последующего прибавления к нему MCND MPY раз. Ход выполнения программы можно просле-
дить по рис. 5.7, на котором показано содержимое регистров и памяти после реализации каждой команды, и по описанию отдельных шагов программы, представленному ниже.
Команда CLRA служит для установки аккумулятора в нуль.
Sap
Команда
PC
A
z
MEM[2C00J- (PROD)
MEM[2C01] (CNT)
0
Ж Ж Ж
2A40
??
?
??
1
CLRA
2A41
00
1
??
??
2
STA PROD
2A44
00
1
00
??
3
LDA MPY
2A47
05
0
00
77
4
STA CNT
2A4A
05
0
00
05
5
LDA CNT
2A4D
05
0
00
05
€
BEQ OUT
2A4F
05
0
00
05
7
ADDA #-1
2A51
04
0
00
05
8
STA CNT
2A54
04
0
00
04
9
LDA PROD
2A57
00
1
00
04
10
ADDA MCND
2A5A
17
0
00
04
11
STA PROD
2A5D
17
0
17
04
12
BRA LOOP
2A4A
17
0
17
04
5
LDA CNT
2A4D
04
0
17
04
*6
BEQ OUT
2A4F
04
0
17
04
5
LDA CNT
2A4D
03
0
2E
03
6
BEQ OUT
2A4F
03
0
2E
03
5
TDA CNT
2A4D
02
0
45
02
6
BEQ OUT
2A4F
02
0
45
02
5
LDA CNT
2A4D
01
0
5C
01
$
BEQ OUT
2A4F
01
0
5C
01
5
LDA CNT
2A4D
00
1
73
00
6
BEQ OUT
2A5F
00
1
73
00
13
LDA PROD
2A62
73
0
73
00
14
OMP 1000H
1000
73
0
73
00
Рис. 5.7. Содержимое регистров и памяти после выполнения команд программы умножения.'
Команда STA PROD является командой с обращением к памяти. Два байта, расположенные после кода операции, представляют собой адрес ячейки памяти 2С00Н, по которому* должно быть записано содержимое аккумулятора (ООН).
Команда LDA MPY также является командой с обращением к памяти. По этой команде в аккумулятор производится загрузка содержимого ячейки памяти по адресу 2С02Н.
Команда STA CNT служит для записи множителя в ячейку памяти по адресу 2С01Н и позволяет осуществлять контроль за тем, сколько раз осталось повторить цикл сложения.
С помощью команды LDA CNT содержимое ячейки памяти по адресу 2С01Н загружается в аккумулятор. Известно, что результат выполнения команд, оперирующих данными, оказывает влияние на содержимое триггера Z. Поэтому данная команда устанавливает Z в 1, если CNT=0, и в 0, если CNT#0.
Команда BEQ OUT устанавливает в счетчике команд PC значение, равное 2A5FH, если Z=l. Это означает, что если CNT=0, то в результате выполнения данной команды осуществляется переход к 13-му шагу программы. Во втором байте машинной команды вместо действительного адреса перехода (2A5FH) содержится смещение. В результате соблюдения условия (Z=l) после команды BEQ OUT выполняется не следующая по порядку команда, а та, которая отстоит от нее на 101$ байт. Другими словами, при переходе производится прибавление смещения к измененному значению счетчика команд. При этом следует помнить, что командой, записанной на языке ассемблера, задается действительный адрес перехода; ассемблер вычисляет точное значение смещения для машинной команды и выдает сообщение об ошибке, если адрес находится за пределами области определения.у
По команде ADDA#—1 производится вычитание единицы из содержимого аккумулятора (если, конечно, перед этим не был осуществлен переход к ячейке памяти OUT). Данная команда использует непосредственный операнд. Когда устройство управления дешифрирует код операции 8BH(ADDA#), производится считывание следующего байта из потока команд (FFH) и его прибавление к содержимому аккумулятора А.
По команде STA CNT производится запись содержимого аккумулятора А в память. Данная операция выполняется так, как было описано ранее.
С помощью команды LDA PROD содержимое ячейки памяти по адресу 2С00Н загружается в аккумулятор.
При выполнении команды ADDA MCND содержимое ячейки памяти по адресу 2С03Н прибавляется к содержимому аккумулятора А.
По команде STA PROD модифицированное содержимое аккумулятора записывается в ячейку памяти по адресу 2С00Н.
Команда BRA LOOP служит для безусловной загрузки в счетчик команд величины 2А4АН, что приводит к передаче управления 5-му шагу программы. Адрес перехода 2А4АН формируется путем расширения знака смещения ЕВН, с целью получения 16-разрядного целого числа FFEBH в дополнитель-
ном коде' и последующего прибавления этого числа к измененному значению счетчика команд 2A5FH.
По команде LDA PROD содержимое ячейки памяти по адресу 2С00Н вновь загружается в аккумулятор.
Команда JMP 1000Н обеспечивает безусловную загрузку в счетчик команд величины 1000Н по окончании программы. Считается, что это начальный адрес программы операционной системы, которая управляет машиной по завершении программы пользователя.
Описание процесса выполнения команд с помощью языка Паскаль
Перед тем как ознакомиться с остальными командами, входящими в набор команд процессора Н6809 и не рассмотренными в предыдущем примере, рассмотрим процедуру на расширенном языке Паскаль, которая точно описывает процесс выполнения каждой команды этого процессора. На рис. 5.8 приведена процедура Execute, которая согласуется с описанием основного машинного цикла, представленным на рис. 5.4. Тело процедуры представляет собой оператор CASE, который каждому коду операции присваивает определенное значение. Необходимо внимательно изучить рис. 5.8, чтобы убедиться в правильности понимания операций, выполняемых в соответствии с каждой из обсужденных выше команд, а также получить определенное представление о командах, которые будут рассмотрены ниже.
PROCEDURE Execute;
CONST {Mnemonic-opcode correspondences}
NOP=12H; CLRA=4FH; COMA=43H; NEGA=40H; LDA#=86H; LDA@X=0A6H;
LDA=0B6H; STA@X=0A7H; STA=0B7H; ADDA#=8BH; ADDA=0BBH;
ANDA#=84H; ANDA=0B4H; CMPA#=81H; CMPA=0B1H; LDX#=8EH;
LDX=0BEH; STX=0BFH; CMPX#=8CH; CMPX=0BCH; ADDX#=30H; ADDX=31H; LDS#=8FH; BNE=26H; BEQ=27H; BRA=20H; JMP=7EH; JSR=0BDH; RTS=39H;
VAR opnum : integer;
FUNCTION NextByte : byte; {Get next byte in instruction stream.} BEGIN NextByte := MEM[PC]; PC := PC + 1 END;
PROCEDURE FetchAddr; {Get 2-byte address from instruction stream.} BEGIN EAR[15:: 8] := NextByte; EAR(7::0] := NextByte END;
PROCEDURE TestByte (b : byte); {Set 2 according to byte value.} BEGIN IF b=0 THEN Z:=1 ELSE Z:=0 END;
PROCEDURE TestWord (w : word); {Set 2 according to word value.} BEGIN IF w=0 THEN Z:=1 ELSE Z:=0 END;
PROCEDURE Branch;
BEGIN {Sign-extend offset in EAR[7::0] and add to PC.} IF EAR[7]=0 THEN EAR[15::8] := 0 ELSE EAR [15:: 8] := OFFH; PC := PC 4- EAR;*
END;
BEGIN {Statement part of Execute}
opnum := IR;
CASE opnum OF
NOP:
CLRA: 'BEGIN A := 0; TestByte(A) END;
COMA: BEGIN A := Bcom (A); TestByte(A) END;
NEGA: BEGIN A := -A; TestByte(A) END;
LDA#: BEGIN A := NextByte; TestByte(A) END;
LDA@X: BEGIN-A := MEM[X]; TestByte (A) END;
LDA:BEGIN FetchAddr; A := MEM[EAR]; TestByte(A) END;
STA:BEGIN FetchAddr; MEM[EAR] := A; TestByte(A) END;
STA@X: BEGIN MEM[X] := A; TestByte (A) END;
ADDA#: BEGIN A := A + Nextbyte; TestByte(A) END;
ADDA: BEGIN FetchAddr; A := A + MEM[EAR]; TestByte(A) ,END;
ANDA#: BEGIN A := Band(A,Nextbyte); TestByte(A) END;
ANDA: BEGIN FetchAddr; A ;= Band(A,MEM[EAR]) ; TestByte (A) END;
CMPA#: BEGIN TestByte(A-Nextbyte) END;
CMPA: BEGIN FetchAddr; TestByte(A-MEM[EAR]) END;
LDX#: BEGIN FetchAddr; X := EAR; TestWord(X) END;
LDX:BEGIN FetchAddr; X[15::8] := MEM [EAR];
. X[7:;0] : = MEM[EAR+1]; TestWord(X) END;
STX:BEGIN FetchAddr; MEM[EAR] := X(15; : 8] ;
MEM[EAR+1] :=X[7::0]; TestWord(X) END;
CMPX#: BEGIN FetchAddr; TestWord(X-EAR) END;
CMPX: BEGIN FetchAddr; TestWord(X-(MEM[EAR]IMEM[EAR+1] )) END;, ADDX#: BEGIN FetchAddr; X := X + EAR; TestWord(X) END;
ADDX: BEGIN FetchAddr; X := X + (MEM[EAR] |MEM[EAR+1]) ;
TestWord(X) END; {"I* denotes concatenation of bytes}
LDS#:BEGINFetchAddr; SP := EAR; TestWord(SP)END;
BNE:BEGINEAR[7::0] := NextByte; JF Z=0THENBranchEND;
BEQ:BEGINEAR[7::0] := NextByte; *IF Z=1THENBranch* END;
BRA:BEGINEAR[7:;0] := NextByte; BranchEND;
JMP:BEGINFetchAddr; PC := EAR END;
JSR:BEGIN
FetchAddr; SP := SP - 2; {Reserve 2 bytes on stack#} MEM[SP] := PC[15::8]; MEM[SP+1] : = PC[7::0]; (Save PC} PC := EAR; {...and jump to subroutine.}END;
RTS:BEGIN {Restore PC from top of stack.}
PC[15::8] :=MEM[SP]; PC[7::0] :=MEM[SP+1];
SP := SP + 2; {Pop stack.} *END;
.END; {End CASE}
END; {End Execute}
Рис. 5.8. Процедура выполнения команд процессора Н6809.
Заметим, что по окончании обычного цикла выборки (рис. 5.4) счетчик команд всегда указывает байт, следующий за кодом операции, независимо от того, является ли данный байт очередным кодом операции или частью текущей команды. Во время исполнительного цикла производится считывание дополнительных 1 или 2 байт (если это необходимо в соответствии с кодом операции команды) и изменение значения счет-
чика команд. Поэтому в конце исполнительного цикла счетчик команд всегда указывает первый байт следующей команды.
Косвенная адресация
В программе, представленной на рис. 5.6, используются только простые переменные и константы. Однако в подавляющем большинстве программ применяются более сложные структуры данных, такие, как массивы, стеки и очереди. Рассмотрим задачу установки пяти элементов массива Q [0...4] в нуль с помощью только тех команд, с которыми мы уже знакомы. Решение
Адрес
жимое' Метка onepau.0nePaH” К^^^^
3000 3001
3004 3007 300А 300D ЗОЮ 3013 3100 3105
ORGЗОООН
4FINIT CLRASet components of Q to zero.
В7 3100STAQFirst component.
B7 3101STAQ+1Second component.
В7 3102STAQ+2Third component.
B7 3103STAQ+3Fourth component.
B7 3104STAQ+4Fifth component.
7E 1000JMP1000H Return to operating system.
ORG3100H
??QRMB5Reserve 5 bytes for array.
ENDINIT
Рис. 5.9. Инициализация массива с помощью команд, использующих прямую адресацию.
этой задачи в виде программы, написанной на языке ассемблера, приведено на рис. 5.9. Следует отметить, что численные значения присваиваются выражениям операндов Q+L Q+2, Q+3 и Q+4 в процессе ассемблирования, а также то, что второй оператор ORG оказывает воздействие только на следующую строку, подлежащую трансляции, и не влияет на адрес той строки, в которой он находится.
Выбор массива, состоящего из пяти элементов, обусловлен тем, что соответствующая программа, например для 100-эле- ментного массива, просто не поместилась бы на одной странице. При прямой адресации, которая используется в большинстве команд процессора Н6809, для получения программы, показанной на рис. 5.9, необходимо знать адреса уже на этапе трансляции. Применение косвенной адресации позволяет устранить эту проблему путем вычисления адреса во время прогона программы. В процессоре Н6809 предусмотрен только один тип косвенной адресации, в соответствии с которым адрес операнда при
выполнении команды поступает из регистра X. Этот способ адресации позволяет загружать данные в аккумулятор из памяти и записывать содержимое аккумулятора в память. Следовательно, для инициализации массива можно организовать такой цикл, при каждом повторении которого в регистре X будет содержаться адрес нового элемента массива. Прежде чем рассмотреть такой цикл, необходимо ввести некоторые команды, оперирующие регистром X.
Ад-
Содержимое
Метке
Код операц.
Операнд
Комментарий
ORG
3000Н
3000
4F
INIT
CLRA
Set components of Q to zero.
3001
8Е 3100
LDX
#Q
Address of first component.
3004
А7
ILOOP
STA
@х
Set MEM[X] to zero.
3005
30 0001
ADDX
#1
Point to next component.
3008
8С 3105
СМРХ
#Q+5
Past last component?
300В
26 F7
BNE
ILOOP
If not, go do some more.
3000
7Е 1000
JMP
1000Н
Return to operating system.
ЗОЮ
ORG
3100Н
3100
??
Q
RMB
5
Reserve 5 bytes for array.
3105
END
INIT
Рис. 5.10. Инициализация массива с помощью команд, использующих косвенную адресацию.
В соответствии с табл. 5.1 в указанный регистр можно загружать непосредственные данные (LDX^addr) длиной 2 байт, которые содержатся во 2-м и 3-м байтах команды. Если в регистр X загружаются данные, поступающие из памяти (LDX addr), то в команде задается адрес 1-го байта слова; при этом в старший байт регистра X поступает величина MEMfaddr], а в младший — MEM[addr-f-l]. Кроме того, содержимое регистра X можно записывать в память, сравнивать его с непосредственным операндом или операндом, находящимся в памяти, а также прибавлять к нему непосредственный операнд или операнд, расположенный в памяти.
В программе, приведенной на рис. 5.10, инициализация массива выполняется с использованием косвенной адресации. Как показано на рис. 5.11, вначале производится такая установка регистра X, чтобы он указывал на первый элемент массива Q, а затем реализуется цикл, при каждом повторении которого содержимое регистра X увеличивается на 1, что приводит к очистке последовательных элементов массива Q. С помощью команды СМРХ производится сравнение содержимого регистра X с непосредственным операндом, который является адресом эле-
мента, расположенного сразу после последнего элемента массива Q. Если эти величины совпадают, то триггер Z устанавливается в 1. Так как регистр X используется для обращения к элементам массива с разными индексами, его называют индексным регистром.
Адрес (б шест надцат. системе) Память
30003000CLRACLRA3001 . LDX#3001 . LDX#3002 3003 3000 300330063007 30083009 300А 3008 300С 3008 300Е 300F3002 3003 3000 300330063007 30083009 300А 3008 300С 3008 300Е 300F_31*- 00".STA8X АЖ# _00 оГСМРХ* 31 __31*- 00".STA8X АЖ# _00 оГСМРХ* 31 _ВНЕ_ " F7IMP1000ВНЕ_ " F7IMP10003100 3101 зюг 310331003100 3101 зюг 3103310000000000000000000000^3100 ]^3100 ]= Выполнено= Выполнено3100 I |» 3105 \3100 I |» 3105 \
3000
3000
CLRA
CLRA
3001 . LDX#
3001 . LDX#
3002 3003 3000 3003
3006
3007 3008
3009 300А 3008 300С 3008 300Е 300F
3002 3003 3000 3003
3006
3007 3008
3009 300А 3008 300С 3008 300Е 300F
_31*- 00".
STA8X АЖ# _00 оГ
СМРХ* 31 _
_31*- 00".
STA8X АЖ# _00 оГ
СМРХ* 31 _
ВНЕ_ " F7
IMP
10
00
ВНЕ_ " F7
IMP
10
00
3100 3101 зюг 3103
3100
3100 3101 зюг 3103
3100
00
00
00
00
00
00
00
00
00
00
^3100 ]
^3100 ]
= Выполнено
= Выполнено
3100 I |» 3105 \
3100 I |» 3105 \
Рис. 5.11. Действие косвенной адресации.
Программа, приведенная на рис. 5.10, занимает меньшее число байтов по сравнению с программой, представленной на рис. 5.9. Кроме того, длина программы на рис. 5.10 для массивов любого размера одна и та же. Чтобы использовать данную программу для массива другого размера, следует только изменить число повторений цикла.
В реальном процессоре 6809, как и в большинстве вычислительных машин, используется много других типов косвенной адресации, которые будут рассмотрены в гл. 7.
Подпрограммы
Подпрограмма является машинным эквивалентом процедур или функций языка Паскаль и представляет собой последовательность команд, которая задается и записывается в память
только один раз и может вызываться из различных точек программы. Для составления подпрограмм на машинном языке необходимо иметь команды, позволяющие сохранять текущее значение счетчика команд при вызове подпрограммы и восстанавливать это значение после ее выполнения.
В процессоре Н6809 вызов подпрограммы и возврат из нее осуществляются с помощью команд JSR и RTS и указателя сте-
Увеличение размера _ стекаУвеличение размера _ стекаОбласть стекаОбласть стека^r^rhhвозрастание значений адресов памяти
Увеличение размера _ стека
Увеличение размера _ стека
Область стека
Область стека
^r
^r
h
h
Рис. 5.12. Стек адресов возврата процессора Н6809:
а — пустой стек; б — стек после вызова одной подпрограммы; в -? стек после вызова вто- . рой подпрограммы.
ка SP. Для любой программы, в которой используются подпрограммы, необходимо зарезервировать небольшую область памяти для организации стека адресов возврата. Для того чтобы указатель стека определял эту область памяти в начале программы, необходимо обеспечить его начальную установку с помощью команды LDS#addr. Как показано на рис. 5.12, указатель стека всегда задает верхний элемент стека, а в случае отсутствия информации в стеке — элемент, смежный с областью памяти, отведенной под стек. Уменьшение значения указателя стека на 1 производится непосредственно перед загрузкой каждого байта в стек, а увеличение на 1 — непосредственно после извлечения байта из стека. Адрес возврата занимает 2 байт.
На рис. 5.13 приведены главная программа с двумя вложенными подпрограммами на языке Паскаль и соответствующие операторы языка ассемблера. Команда JSR addr служит для сохранения адреса следующей команды (подлежащей выполнению по окончании подпрограммы) путем записи его в стек и для перехода к команде, размещенной по адресу addr, которая
является первой командой подпрограммы. После завершения подпрограммы с помощью команды RTS осуществляется перезапись адреса из стека в счетчик команд, что позволяет вернуться к выполнению первоначальной последовательности команд.
Стек является наиболее подходящей структурой для сохранения адресов возврата, так как в случае использования вложен-
Адрес
Содер-М жимое петка
Код опера
щ Операнд Комментарий
ORG
3000Н PROGRAM Sub г s (input, output) ;.
3000.
...SUBR2
* • е
PROCEDURE P2;
• ее
...
• ее
BEGIN
• • •
• ее
• • в
ее»
3055
39
RTS
END;
3056
...SUBR1
PROCEDURE P1;
...
• . .
• ее
BEGIN
* е •
е • •
• • •
• ее
30А2
BD 3000
JSR
SUBR2P2; (Call P2}
30А5
...RET2
е • е
• ее
• • •
• ее
■ ее
e e e
30C2
39
RTS
END;
30C5
8F 3FF8 .MAIN
LDS
#STK+8 BEGIN (Main program)
• • •
е > •
• ее
312F
BD 3056
JSR
SUBR1P1; (Call P1}
3132
...RET1
е . .
e • •
• ее
• ее
ее.
31А7
7Е. 1000-
JMP
1000H END.
ORG
3FF0H
3FF0
.. ♦ВТК
RMB
8
...
END
MAIN
Рис. 5.13. Программа с двумя вложенными подпрограммами.
пых подпрограмм в него можно записать несколько адресов возврата. Число уровней вложения при этом ограничивается только размером области памяти, отведенной программистом под стек.
Пример программы, в которой используются подпрограммы, приведен на рис. 5.14. Прежде чем приступить к рассмотрению этой программы, ознакомимся с некоторыми используемыми в ней дополнительными псевдокомандами языка ассемблера.
FCW (Form Constant Word — Формирование константы длиной в 1 слово). Данная псевдокоманда используется для записи определенной 16-разряднсгй величины в два последовательных байта памяти, при этом вначале производится запись в старший байт. Псевдокоманда FCW, так же как и псевдокоман-
Адрес
^мпГ М = ТКа ж и мое
операц.0перанл КомментаРий
ORG
2000H
2000 8F 201А MAIN
LDS
#STKE Initialize SP.
2003 BE 201А
LDX
TWORD Get test word.
2006 BD 201С
JSR
WCNT1S Count number of 1s in it.
2009 7Е 1000
200СSYSRET
JMP EQU
SYSRET Return to operating system. 1000H Operating system address.
200С ??STK
201ASTKE
RMW
EQU
7Space for 7 return addresses.
*Initialization address for SP.
201A 5B29TWORD
201C*
201C*
201C*
FCW
Count
Enter
5B29H Test-word to count 1s.
the number of * 1’bits in a word, with word in X, exit with count in A.
201C BF 2032 WCNT1S
STX
CWORD Save input word.
201F B6 2032
LDA
CWORD Get high-order byte.
2022 BD 2035
JSR
BCNT1S Count 1s.
2025 B7 2034
STA
W1CNT Save 11* count.
2028 B6 2033
LDA
CWORD+1 Get low-order byte.
202B BD 2035
JSR
BCNT1S Count 1s.
202E BB 2034
2031 39
ADDA RTS
W1CNT Add high-order count. Done, return.
2032 ??CWORD
RMW
1Save word being counted.
2034 ??W1CNT
2035*
2035*
2035*
RMB
Count
Enter-
1Save number of 1s.
number of • 11 bits in a byte.
with byte in A, exit with count in A.
2035 B7 2061 BCNT1S
2038 4F
2039 B7 2062
STA CLRA
STA
CBYTE Save input byte./
Initialize 11* count.
B1CNT
203C 8E 2059
LDX
#MASKS Point to 1-bit masks.
203F A6BLOOP
LDA
@XGet next bit mask.
2040 B4 2061
ANDA
CBYTE Is there a '1' there?
2043 27 08
BEQ
BNO1Skip if not.
2045 B6 2062
LDA
B1CNT Otherwise increment
2048 8B 01
204A B7 2062
ADDA STA
#111* count.
B1CNT
204D 30 0001 BNO1
ADDX
#1Point to next mask.
2050 8C 2061
CMPX
#MASKE Past last mask?
2053 26 EA
BNE
BLOOP Continue if not.
2055 B6 2062
2058 39
2059*
LDAB1CNT Put total count in A.
RTSReturn.
Define 1-bit masks to test bits of byte.
2059 804C2010MASKS
205D 08040201
2061MASKE
FCB
EQU
' 80H, 40H, 20H, 10H, 8H, 4H, 2H, 1H
*Address just after table.
2061 ??CBYTE
RMB
1Save byte being counted.
2062 ??B1CNT
.2063
RMB
END
1Save 11* count.
MAIN
Рис. 5.14. Программа, в которой для подсчета числа единичных разрядов в слове используются подпрограммы.
да FCB, может иметь несколько операндов, отделенных друг от друга запятыми.
RMW (Reserve Memory Words — Резервирование слов памяти). Если операнд имеет значение, равное п, то выполнение псевдокоманды RMW вызывает пропуск 2п байт памяти без записи в них каких-либо команд или данных.
EQU (Equate — Присваивание). С помощью псевдокоманды EQU идентификатору, находящемуся в поле метки, присваивается значение из поля операнда, а не из поля адреса. Поэтому во время трансляции идентификатор является синонимом константы.
* (Строки коментария). Ассемблер игнорирует любую строку, начинающуюся с символа звездочки.
* (Счетчик ячеек программы). Использование символа* в каком-либо выражении служит для обозначения текущего адреса, по которому осуществляется трансляция.
Программа, представленная на рис. 5.14, состоит из главной программы и двух подпрограмм. Главная программа производит установку указателя стека, так чтобы он задавал стек из семи слов. Для данной программы достаточно было бы иметь стек из двух слов. Однако на случай возможных ошибок программирования, модификаций или прерываний (гл. 11) область памяти, отводимую под стек, полезно увеличить.
Главная программа обеспечивает загрузку 16-разрядного слова в индексный регистр X и вызывает подпрограмму WCNT1S, с помощью которой осуществляется подсчет числа единиц, записанных в индексном регистре X. Подпрограмма WCNT1S разбивает содержимое' этого регистра на 2 байт и вызывает подпрограмму BCNT1S для подсчета числа единиц в каждом байте. Эффективность использования подпрограмм очевидна, поскольку подпрограмму BCNT1S можно вызывать более одного раза и при этом каждый раз производить преобразование различных байтов.
На рис. 5.15 показано содержимое стека после выполнения каждой команды, влияющей на его состояние. При возврате из подпрограммы WCNT1S в главную программу стек становится пустым, а данные о числе подсчитанных единиц находятся в аккумуляторе. Главная программа заканчивается переходом к операционной системе.
Теперь полезно рассмотреть подпрограммы, приведенные на рис. 5.14. При входе в подпрограмму WCNTIS считается, что входное слово находится в индексном регистре X. Это первый из рассмотренных нами случаев, когда индексный регистр используется в качестве удобного места для хранения 16-разряд- ных данных, а не адреса. С помощью команды STX CWORD подпрограмма WCNT1S осуществляет запись содержимого ин-
дексного регистра X в 2 байт памяти, данные из которых считываются по отдельности с помощью команд LDA CWORD и LDA CWORD+1 и передаются подпрограмме BCNT1S.
При входе в подпрограмму BCNT1S входной байт, который должен находиться в аккумуляторе А, записывается в ячейку памяти CBYTE. После этого производится проверка каждого разряда на наличие в нем 1 и запись результата в ячейку памяти B1CNT. Проверка осуществляется с помощью таблицы, состоящей из 8 маскирующих байтов, у каждого из которых
/4^/75^
2024
2025
2026
2027
2028
2029
202А SP->
SP^
SP^-
SP->-
SP->-
SP^-
SP^
20
20
35
3E
20
20
20
20
20
09
09
09
09
09
Команда #
LDS STAC Kt
JSR WCNT1
s t
JSR 3CNTU
S
RTS
JSR BCNT1
s
RTS
RTS
Рис. 5.15. Содержимое стека после выполнения команд программы, приведенной на рис. 5.14.
в разных разрядах находится по одной 1. Индексный регистр содержит адрес маскирующего байта, а содержимое ячейки памяти CBYTE соединяется с каждым из маскирующих байтов с помощью команды ANDA@X. При каждом повторении цикла команда ANDA приводит к получению ненулевого результата только в том случае, если в проверяемом разряде ячейки памяти CBYTE содержится 1.
Подобно процедурам и функциям в языке Паскаль, подпрограммы являются ключом к структуре программ, написанных на языке ассемблера. Обычно программа делится на много модулей, каждый из которых представляет собой подпрограмму, имеющую вход, выход и локальные данные. Подробно подпрограммы будут рассмотрены в гл. 9.
ПРОЦЕССОРЫ С РЕГИСТРАМИ
ОБЩЕГО НАЗНАЧЕНИЯ
В рассматриваемых в данном разделе процессорах арифметические и логические операции, а также передача данных производятся с использованием набора регистров общего назначения. Любой из этих регистров может использоваться для хранения данных, необходимых для выполнения команды, или для задания адреса. Обычно процессоры включают 8 или 16 регистров общего назначения, длина каждого из которых состав-
ляет 16—32 разрядов. Примерами процессоров с регистрами общего назначения являются PDP-11, Z8000 и 68000.
Организация процессора
с регистрами общего назначения
Структура гипотетического процессора с регистрами общего назначения приведена на рис. 5.16. В состав этого процессора входят те же регистры и устройства, что и в микропроцессор
Z8000 фирмы Zilog, поэтому назовем его Н8000. По сравнению с процессором Н6809 длина регистра команд IR процессора Н8000 увеличена до 16 разрядов. Кроме того, в нем отсутствуют указатель стека и индексный регистр, а вместо аккумулятора имеется шестнадцать 16-разрядных регистров общего назначения R0—R15. В процессоре Н8000 функции указателя стека, используемого в Н6809, реализует регистр R15, а функции индексного регистра X может выполнять любой из регистров, за исключением R0. Как и прежде, регистр (триггер) Z устанавливается в соответствии с результатами выполнения любой команды, оперирующей данными.
Набор регистров процессора Н8000, используемый для программирования, показан на рис.. 5.17. Так как длина адресного слова составляет 16 разрядов, то объем адресуемой памяти включает 64К байт (см. рис. 5.1, в). Арифметические и логические операции, а также передача данных могут выполняться над словами (с использованием 16-разрядного регистра) и над байтами (с использованием половины регистра). При операциях над байтами первые восемь 16-разрядных регистров служат в качестве шестнадцати 8-разрядных регистров, в то время как последние восемь 16-разрядных регистров остаются неза- действованными.
158_7о
||рс
RO R1 R2 R3 R4 R5 R6 R7 R8 R9 R1O
R11 R12
R13 R14
R15(SP)
Z
□□Рис. 5.17. Регистры процессора Н8000, используемые для программирования.
□
□
/5
14
13
9
8 7
4 3О
Формат 1 mode
кап
7в ^з1
гедг
15
14 13
9 87
4 30
Формат 2 mode
коп
% год1
Вспом. коп
15
14 13
8 7
4 3О
Формат 3 mode
коп
гед!
Вспом. коп
15
12 11
8 7
О'
Формат 4
коп
^вия^сс) Смещение (offset)
15
12 11
8 7 6
0
Формат 5
коп
reg!
W/B Смещение (disp)
15
0
Формат 6 I
коп
Рис. 5.18. Форматы команд процессора Н8000.
Форматы команд
Команды процессора Н8000 состоят из одного или двух слов. Так как для различных команд требуются неодинаковое число и разные типы операндов, то процессор Н8000, как и большинство реальных процессоров, имеет несколько форматов команд. В первом слове каждой команды процессора Н8000 содержатся код операции, а также другая информация, такая, как номер регистра и способ адресации. Второе слово, если оно имеется, включает непосредственный операнд или адрес памяти. На рис. 5.18 показано шесть разных форматов, используемых для первого слова команд процессора Н8000.
Большинство команд Н8000 в зависимости от содержимого разряда W/B (Слово/Байт) оперирует словами или байтами (для слов W/B = l). Операнд чаще всего задается с помощью полей mode (способ адресации) и regl. В командах с двумя операндами вторым операндом является содержимое одного из регистров общего назначения, для задания которого служит поле reg2.
Способы адресации
Задание операнда с помощью полей mode и regl осуществляется так, как показано в табл. 5.2. Команды, использующие регистровую или косвенную регистровую адресацию, состоят из одного слова. Для остальных способов адресации требуется второе слово. В последнем случае программист может записать в оператор языка ассемблера любое выражение (ехрг); ассемблер вычисляет выражение и помещает его значение во второе слово.
Первые три способа адресации, представленные в табл. 5.2, предусмотрены и для процессора Н6809. При адресации с ин-
Таблица 5.2. Способы адресации, применяемые в процессоре Н8000
Код способа адресации (поле mode)
Регистр, задаваемый полем reg 1
Обозначение способа адресации на языке ассемблера
Способ адресации
Операнд
00
R0
#ехрг
Непосредственная
ехрг
00
RI—R15
©regl
Косвенная регистровая
MEM [regl]
01
R0
ехрг
Абсолютная
МЕМ [ехрг]
МЕМ [ехрг+regl]
01
RI—R15
ехрг (regl)
С индексированием
10 и
R0—R15
R0—R15
regl
Регистровая (Не используется)
regl
дексированием исполнительный адрес вычисляется путем объединения (сложения) содержимого регистра со вторым словом команды (данный способ адресации рассматривается в разд. 7.3.2). Регистровая адресация позволяет командам оперировать регистрами без выборки операндов из памяти. Помещая часто используемые переменные в регистры, можно значительно быстрее выполнять программы, чем при их обработке с помощью процессора с аккумулятором.
Набор команд
Команды процессора Н8000 сведены в табл. 5.3; здесь операнд opl определяется состоянием полей regl и mode команды. В таблице приведено пять команд с двумя операндами, каждая из которых имеет аналог среди команд процессора Н6809°. Не все способы адресации могут быть использованы для реализации каждой команды. Например, непосредственную адресацию нельзя применять с командой ST(B), поскольку последняя служит для включения содержимого регистра в последовательность команд.
В трех командах с одним операндом CLR(B), СОМ (В) и NEG(B) операнд задается точно так же, как и opl в командах с двумя операндами. С помощью этих команд можно производить очистку ячеек памяти и регистров, а также получать дополнительный и обратный код их содержимого.
Каждая из восьми первых команд может использоваться для выполнения операций над словами или байтами, что определяется состоянием разряда W/B. Для указания операций над байтами к мнемоническому обозначению команд языка ассемблера процессора Н8000 добавляется символ В. Например, команда CLR R5 приводит к установке 0 в обоих байтах регистра R5, в то время как команда CLRB RH5 служит для установки 0 только в старшем байте этого регистра.
Команды JP и CALL задают единственный операнд, которым должно быть слово, записанное в памяти, а не непосредственный операнд или содержимое регистра. Полагая, что слово представляет собой команду, команда JP позволяет осуществить переход к этой команде. Для этого значение счетчика команд устанавливается равным адресу этого слова. Команда CALL является командой вызова подпрограммы, идентичной команде JSR, и служит для записи адреса возврата в стек и последующего перехода к подпрограмме. В качестве указателя
*> Как и в процессоре Н6809, все эти команды процессора Н8000 оказывают воздействие на состояние триггера Z. Однако в реальном процессоре Z800O операции загрузки и записи не влияют на какие-либо признаки состояния.
Таблица 5.3. Набор команд процессора Н8000
Мнемоническое обозначение
Операнды
Формат
Код операции (шест- надцат.)
Вспомогательный код операции (Аихор) (шестнадцат.)
Описание операции
LD(B)
reg2,opl
1
10
Загрузка opl в reg2
Запись reg2 в opl
ST(B)
reg2,opl
1
17
CMP (В)
reg2,opl
1
05
Установка Z в соответствии с результатом операции reg2—op 1
ADD (В)
reg2,opl
1
00
Прибавление opl к reg2
Логическая операция И над opl и reg2
AND (В)
reg2,opl
1
03 "
CLR(B)
opl
2
06
8 .
Установка opl в 0
СОМ(В)
opl
2
06
0
Получение обратного кода для opl
Получение дополнительного кода для opl
NEG(B)
opl
2
06
2
JP
opl
3
IF
8
Переход по адресу, определяемому opl
CALL
opl
3
IF
0
Вызов подпрограммы по адресу, определяемому opl
JR
cc,offset
4,
Е
Переход, если выполняется условие сс
D(B)JNZ
RET
NOP
regl,disp
5
6
6
F
9Е08
8D07
Уменьшение regl на 1 и переход, если результат не равен 0
Возврат из подпрограммы
Нет операции
Примечания. Обозначение (В) служит для. указания команд, которые используются для выполнения операций как над байтами, так и над словами. Например, команда LD служит для загрузки слова, a LDB — для загрузки байта.
regl и reg2 — любые регистры из набора регистров общего назначения.
opl — операнд, определяемый с помощью одного из способов адресации, приведенных в табл. 5.2. Однако с командами ST(B), JP и CALL нельзя использовать регистровую и непосредственную адресацию; последний способ адресации не применяется также при работе с командами CLR(B), СОМ(В) и NEG(B).
сс — один из кодов условий перехода, приведенных в табл. 5.4.
offset (смещение) — 8-разрядиое целое число со знаком, которое в случае выполнения условия перехода умножается на 2 и прибавляется к содержимому счетчика команд PC.
dlsp (смещение) — 7-разрядное неотрицательное число, которое умножается на 2 и вычитается из содержимого счетчика команд, если содержимое регистра отлично от 0.
Коды операций команд являются такими же, как у команд процессора Z8000 фирмы Zilog. Мнемонические обозначения и запись команд иа языке ассемблера в некоторых случаях отличаются от используемых в разных языках ассемблера процессора Z8000.
стека в процессоре Н8000 всегда используется регистр R15, операции с которым выполняются так же, как и с указателем стека в процессоре Н6809. Установку регистра R15 программист может выполнить в начале программы. Команда RET, подобно команде RTS процессора Н6809, служит для осуществления возврата из подпрограммы путем пересылки содержимого стека в счетчик команд.
Таблица 5.4. Условия переходов, используемые в процессоре Н8000
Запись команд перехода на языке ассемблера
сс (в шестнадцатеричной системе)
Пояснения
JR offset
8
Переход без выполнения каких-либо ус* ловий
JR EQ,offset
6
Переход, если 2=1 (результат равен нулю)
Цереход, если 2=0 (результат больше или меньше нуля)
JR NE,offset
E
Команда условного перехода JR сс,offset содержит 8-раз- рядное поле смещения со знаком (offset) и 4-разрядное поле сс, которое служит для задания условия, подлежащего проверке (табл. 5.4.) Все команды процессора Н8000 должны начинаться на границе слова. Поэтому переход всегда должен выполняться на четное число байтов. Данное обстоятельство используется в процессоре Н8000 для увеличения диапазона адресов переходов. Перед прибавлением к содержимому счетчика команд смещение со знаком умножается на 2. Следовательно, если значение смещения равно +5, то переход (в случае выполнения условия) осуществляется на пять слов вперед. В программах, написанных на языке ассемблера, поле операнда команды JR задает действительный 16-разрядный адрес перехода, а ассемблер вычисляет соответствующее 8-разрядное смещение для машинной команды, выдавая сообщение об ошибке, если адрес перехода выходит за пределы допустимого диапазона.
Команда D(B)JNZ представляет собой команду управления циклом. Она обеспечивает уменьшение содержимого используемого регистра на 1 и последующий переход к началу цикла, если полученное значение отлично от нуля. Семиразрядное смещение disp этой команды интерпретируется как положительное число, которое умножается на 2 и вычитается из содержимого счетчика команд, если содержимое регистра не равно 0. Команда DJNZ языка ассемблера, подобно команде JR, задает действительный 16-разрядный адрес перехода, а ассемблер вычисляет соответствующее 7-разрядное смещение для машинной команды.
Примеры программ
Программа умножения двух чисел для процессора Н6809 (рис. 5.6), переписанная применительно к процессору Н8000, приведена на рис. 5.19. Данная программа предназначена для обработки байтовых данных, однако ее можно легко модифицировать для выполнения операций над словами. Отметим, что символ В в командах CLRB, ADDB и др. указывает на то, что
Адрес
Содер^ жимое
Метка
Код операц.
Операнд
Комментарий
ORG
2A40H
Multiply MdrJD by MPY.
2А40
8С18
START
CLRB
RH1
Keep product in RH1.
2А42
6000 2А52
LDB
RH0,MPY
Keep iteration count in RH0.
2А46
Е603
JR
EQ,OUT
Done if multiplier zero.
2А48
4001 2А53
LOOP
ADDB
RH1,MCND
Add MCND to product.
2А4С
F003
DBJNZ
RHO,LOOP
Multiplier zero yet?
2А4Е
5Е08 1000
OUT
JP
1000H
Return with product in RH1.
2А52
05
MPY
FCB
5
Multiplier value.
2А53
17
MCNp
FCB
23
Multiplicand value.
2А54
END
START
Рис. 5.19. Программа умножения, выполняемого путем многократного повторения операции сложения, составленная для процессора Н8000.
данные команды предназначены для обработки байтов, а не является обозначением регистра В.
Несмотря на то что отдельные команды процессора Н8000 имеют большую длину по сравнению с командами процессора Н6809, программа, представленная на рис. 5.19, является менее длинной. Это связано с использованием регистров общего назначения, которые позволяют хранить произведения и данные о числе циклов и исключить таким образом необходимость их многократной передачи из аккумулятора в память и обратно, как в процессоре Н6809. Кроме того, наличие команды DBJNZ (Уменьшение значения байта и переход, если не нуль) позволяет организовать очень короткий и эффективный внутренний цикл.
На рис. 5.20 приведена программа инициализации массива, предназначенная для процессора Н8000, которая является вариантом программы, представленной на рис. 5.10. В данном случае массив Q описывается как массив слов, однако программа может быть легко изменена с целью обработки массива байтов. В качестве индексного регистра, который указывает на элемент массива Q, используется регистр R1. Для этой пели можно было бы выбрать любой из регистров общего наз-
начения, за исключением R0 или R15. (Объясните, почему.) В действительности оказывается, что данная программа для процессора Н8000 занимает на 5 байт больше, чем соответствующая программа для процессора Н6809. Это обусловлено тем, что обращение ко всем переменным в процессоре с аккумулятором может осуществляться с помощью более коротких команд.
Программа подсчета числа единиц, соответствующая программе рис. 5.14, приведена на рис. 5.21. В ней удачно сочета-
Адрес
Содержимое
Метка операц,
Операнд
Комментарий
ORG
3000H
Initialize an array to 0.
3000
2101 3100
INIT LD
R1,#Q
Address of first component.
3004
0D18
ILOOP CLR
@R1
Clear a word.'
3006
0101 0002
ADD
R1,#2
Point to next word.
300А
0В01 310А
CMP
R1,#Q+10
Past the end?
300Е
EEFA
JR
NE,ILOOP
Continue if not.
ЗОЮ
5Е08 1000
JP
1000H
3014
ORG
3100H
3100
77
QRMB
10.
Reserve 5 words.
310А
END
INIT
Рис. 5.20. Программа инициализации массива, составленная для процессора Н8000.
ются команды, оперирующие байтами данных, в которых подсчитывается число единиц, и команды, оперирующие словами данных (TWORD и подсчет единиц) и адресами (указатели таблицы масок). Заметим, что подпрограмма WCNT1S разбивает входное слово на байты путем обращения к старшему и младшему байтам R0 с помощью команд LDB.
Программа, представленная на рис. 5.21, хорошо организована, поэтому не удивительно, что она занимает меньшее число байтов по сравнению с соответствующей программой процессора Н6809. Еще более важным обстоятельством является то, что она включает меньшее число выполняемых команд (21 вместо 27). Так как программисты обычно стремятся каждый день записывать постоянное число команд независимо от используемого языка, то их работа может быть более производительной при составлении программ для процессоров с регистрами общего назначения, а не процессоров с аккумуляторами.
МАШИНЫ СО СТЕКОВОЙ ОРГАНИЗАЦИЕЙ
У машины со стековой организацией нет ни регистров общего назначения, ни аккумуляторов, а имеется указатель стека, который задает стек, организованный в памяти. Все команды
Базовая структура ЭВМ193
Ад- .Содер- рес жимое
Метка операцОперанд Комментарий
2000 210F 201Е 2004 6100 5В29 2008-5F00 2020 200С 5Е08 1000 2010 2010 ??
201Е
201Е 5В29
2020
2020
2020
2020
2020 А020
2022 5F00 2032 2026 А131-
2028 А082‘ 202А 5F00 2032 202Е8131.
2030 9Е08
2032
2032'
2032•,
2032
2032
2032
2032 8D38
2034 2104 204Е 2038 204А.
203А 862А 203С Е602 203Е 0103 0001 2042 0104 0001 2046 0В04 2056 204А ЕЕЕС 204С 9Е08 204Е
204Е 80402010
2052 0804020V
2056'
2056
ORG2000Н‘
MAINLDR15,#STKEInitialize SP.
LDR0,TWORDGet test word.
CALLWCNT1SCount number of 1s in it.
JPSYSRETReturn to operating system*
SYSRETEQU1000HOperating system address.
STACK RMW7Space for 7 return addresses
STKEEQU*Stack initialization address
TWORDFCW5B29H.Test wor;d to count 1s.
*Countthe number of 11' bitsin a word.
*R0 =input word on entry
*R1 =bit count on exit
WCNT1S LDBRH2,RH0Count 1s in high byte.
CALL BCNT1S.
LDR1,R3Put high byte count in R1.
LDBRH2,RL0Count 1s in low byte.
CALL BCNT1S'
ADDR1/R3Add low byte count to R1*
.RETDone, return.
*Countthe number of *1’ bitsinabyte#
*RH2=input byte on entry
*RL2=temporary byte valueforANDing
*R3=bit count on exit
*R4=pointer to bit masks
BCNT1S CLRR3Clear count.-
LDR4,#MASKS Point to 1-bit masks*
BLOOPLDBRL2,@R4Get next bit mask*
ANDB RL2,RH2 Is there a 1V there?
JREQ,BNO1Skip if not.
ADDR3,#fOtherwise increment counte
BNO1ADD •R4,#1Point to next mask*
CMPR4,#MASKEPast last mask?
JRNE,BLOOPContinue if. not.
RETe.Else return.
Define 1-bit masks to test bits of byte*
MASKS FCB80H,40H,20H,10H,8H,4H,2H,1H
MASKE EQU *‘ End of mask table.
ENDMAIN .
Рис. 5.21. Программа подсчета числа единичных разрядов в слове.
в такой машине выполняются с участием стека. Многие настольные и карманные калькуляторы (а также имитаторы) фирмы Hewlett-Packard представляют собой машины со стековой организацией. Мини-ЭВМ НР300 и НР3000 также являются машинами данного типа. Кроме того, большинство процессоров как с аккумуляторами, так и с регистрами общего назначения 13—1393
имеют хотя бы несколько команд, предназначенных для работы со стеком.
Ни один из процессоров, рассматриваемых в части III данной книги, не является машиной с чисто стековой организацией. Однако мини-ЭВМ с регистрами общего назначения PDP-11 фирмы Digital Equipment имеет ряд команд, позволяющих легко моделировать команды такой машины. Изучить архитектуру гипотетической машины со стековой организацией, работа которой основана на использовании этих команд мини-ЭВМ PDP-11, очень полезно.
Построение машины со стековой организацией
На рис. 5.22 приведена структура гипотетической машины со стековой организацией. В гл. 13 будет показано, что моделирование такой машины легко выполнить с помощью
Рис. 5.22. Структура гипотетической машины со стековой организацией (НИ).
мини-ЭВМ PDP-11, поэтому назовем гипотетическую машину НИ.
Структура машины НИ совпадает со структурой процессора Н6809, за исключением того, что вместо 8-разрядного аккумулятора А, используемого в Н6809, теперь введен 1б-разряд- ный регистр временного хранения данных TEMP. Как следует из рис. 5.23, этот регистр недоступен программисту; он служит для временного хранения операнда при выполнении каждой команды.
Машина НИ имеет память с побайтовой адресацией емкостью 64К байт, структура которой приведена на рис. 5.1,6, поэтому длина адреса составляет 2 байт. Данные, которыми оперирует программа машины НН, хранятся в стеке, организованном в памяти. Рассматриваемые здесь команды служат для
выполнения операции только над 16-разрядными (2-байтовы* ми) данными. Как показано на рис. 5.24, часть памяти с большими значениями адресов отведена под стек (при заполнении
Рис. 5.23. Регистры процессора НИ, используемые для программирования.Рис. 5.23. Регистры процессора НИ, используемые для программирования.стека адреса его элементов уменьшаются), а часть памяти с меньшими значениями адресов используется для хранения команд и «глобальных» данных. Указатель стека задает верхушку стека в соответствии с теми же договоренностями, что и в случае указателя стека процессора Н6809. Индексный регистр X обычно содержит адрес ячейки памяти, находящейсястека адреса его элементов уменьшаются), а часть памяти с меньшими значениями адресов используется для хранения команд и «глобальных» данных. Указатель стека задает верхушку стека в соответствии с теми же договоренностями, что и в случае указателя стека процессора Н6809. Индексный регистр X обычно содержит адрес ячейки памяти, находящейся15158 78 7Адрес(б шветнабцат.системе)Адрес(б шветнабцат.системе)Программыи данныеПрограммыи данные0000 0002 0004 000600080000 0002 0004 00060008FFF8FFF8СтекСтекНачальноеположенние 8РНачальноеположенние 8РFFFAFFFCFFFEFFFAFFFCFFFEРис. 5.24. Организация памяти машины НИ.Рис. 5.24. Организация памяти машины НИ.внутри стека. Область памяти рядом с этой ячейкой называется кадром стека, поэтому индексный регистр можно назвать указателем кадра стека (FP).5.4.2. Набор командНабор команд машины НИ приведен в табл. 5.5. Команды первой группы используются для загрузки в стек и извлечения из него данных. Команда PUSH addr, 2 байт которой следую* 13»внутри стека. Область памяти рядом с этой ячейкой называется кадром стека, поэтому индексный регистр можно назвать указателем кадра стека (FP).5.4.2. Набор командНабор команд машины НИ приведен в табл. 5.5. Команды первой группы используются для загрузки в стек и извлечения из него данных. Команда PUSH addr, 2 байт которой следую* 13»
Рис. 5.23. Регистры процессора НИ, используемые для программирования.
Рис. 5.23. Регистры процессора НИ, используемые для программирования.
стека адреса его элементов уменьшаются), а часть памяти с меньшими значениями адресов используется для хранения команд и «глобальных» данных. Указатель стека задает верхушку стека в соответствии с теми же договоренностями, что и в случае указателя стека процессора Н6809. Индексный регистр X обычно содержит адрес ячейки памяти, находящейся
стека адреса его элементов уменьшаются), а часть памяти с меньшими значениями адресов используется для хранения команд и «глобальных» данных. Указатель стека задает верхушку стека в соответствии с теми же договоренностями, что и в случае указателя стека процессора Н6809. Индексный регистр X обычно содержит адрес ячейки памяти, находящейся
15
15
8 7
8 7
Адрес
(б шветнабцат.системе)
Адрес
(б шветнабцат.системе)
Программыи данные
Программыи данные
0000 0002 0004 0006
0008
0000 0002 0004 0006
0008
FFF8
FFF8
Стек
Стек
Начальноеположенние 8Р
Начальноеположенние 8Р
FFFA
FFFC
FFFE
FFFA
FFFC
FFFE
Рис. 5.24. Организация памяти машины НИ.
Рис. 5.24. Организация памяти машины НИ.
внутри стека. Область памяти рядом с этой ячейкой называется кадром стека, поэтому индексный регистр можно назвать указателем кадра стека (FP).
5.4.2. Набор команд
Набор команд машины НИ приведен в табл. 5.5. Команды первой группы используются для загрузки в стек и извлечения из него данных. Команда PUSH addr, 2 байт которой следую* 13»
внутри стека. Область памяти рядом с этой ячейкой называется кадром стека, поэтому индексный регистр можно назвать указателем кадра стека (FP).
5.4.2. Набор команд
Набор команд машины НИ приведен в табл. 5.5. Команды первой группы используются для загрузки в стек и извлечения из него данных. Команда PUSH addr, 2 байт которой следую* 13»
Таблица 5.5. Набор команд процессора НИ
Мнемоническое обозначение
Операнды
Длина команды, байт
Код операции (шест- надцат.)
Описание операции
PUSH
addr
3
01
Загрузка MEMW[addr] в стек
PUSH
# data
3
02
Загрузка данных data в стек
PUSH
offset (X)
2
03
Загрузка MEMW[X +offset] в стек
PUSHT
1
04
Загрузка TOS в стек
PUSHS
1
05
Загрузка SOS в стек
PUSHX
1
06
Загрузка X в стек
POP
addr
3
07
Извлечение TOS из стека и запись в MEMW[addr]
POP
offset (X)
2
08
Извлечение TOS из стека и запись в MEMW[X+offset]
CLR
1
10
Установка TOS в 0
COM
1
11
Получение обратного кода TOS
Получение дополнительного кода TOS
NEG
1
12
SWAB
1
13
Обмен байтов TOS
ADD
1
14
Извлечение TOS из стека и прибавление к новому значению TOS
AND
1
15
Извлечение TOS из стека и выполнение логической операции И над этим и новым значением TOS
CMP
1
16
Извлечение TOS из стека и сравнение его с новым значением TOS
MUL
1
17
Извлечение TOS и SOS из стека и загрузка в стек произведения TOS’SOS (результат умножения представляет собой одно слово, переполнение не учитывается)
LD
r,# data
3
20+г
Загрузка данных в г
LD
r,addr
3
22+г
Загрузка MEMW[addr] в г
LDXS
r^ddr
1
24
Загрузка копии SP в регистр X
ST .
3
26+г
Запись г в MEMW[addr]
ADD
r, # offset
2
28+г
Прибавление offset к г
VAL
1
30
Замена TOS на MEMW[TOS]
STOW
1
31
Запись TOS в MEMW[SOS] и извлечение из стека TOS и SOS
BNE
offset
2
40
Переход, если Z=0
BEQ
offset
2
41
Переход* если Z=1
BRA
offset
2
42
Переход без выполнения каких-либо условий
JMP
addr
3
50
Переход по адресу addr
Продолжение табл. 5.5'
Мнемоническое обозначение
Операнды
Длина команды, байт
Код операции (шест- надцат.)
Описание операции
JSR
addr
3
51
Переход к подпрограмме по
адресу addr
RTS
1
52
Возврат из подпрограммы
NOP
1
00
Нет операции
Примечания, data — 16-разрядное значение данных; addr — 16-разрядный адрес памяти; offset —- 8-разрядное целое число со знаком; г — регистр X или SP (если r=SP, то к коду операции прибавляется 1); MEMW[n]—слово памяти по адресу n, п должно быть четным; TOS — слово в верхушке стека; SOS — второе слово стека, считая от верхушки.
Приведенные здесь команды процессора НИ и соответствующие им команды миниЭВМ PDP-11 имеют разные коды операций, мнемонические обозначения и длину.
щих за кодом операции задают абсолютный адрес, позволяет записывать в стек любое слово памяти. Точно так же с помощью команды POP addr слово, находящееся в верхушке стека (TOS), может быть переслано в любую ячейку памяти. При
PUSH1OOOH PUSH 1002Н PUSH1000H POP юозд
Рис. 5.25. Содержимое памяти после выполнения операций записи в стек и считывания из стека.
загрузке слова в стек содержимое указателя стека уменьшается на 2; после этого слово записывается в ту ячейку памяти, на которую указывает новое значение SP (оно должно быть четным).. Извлечение слова из стека производится путем записи его верхнего элемента в заданную ячейку памяти, после чего значение указателя стека увеличивается на 2. На рис. 5.25 приведено содержимое памяти при выполнении последователь-
юности операций загрузки данных в стек и извлечения данных из стека.
Имеются также и другие команды типа PUSH и POP. Команда PUSH #data позволяет помещать в стек непосредст-
/МЭД #4321Н PUSH 1002Н PUSH-2 (X) P0P2W
LDX 8PUSH О (X)POP 1000Н
Рис. 5.26. Содержимое стека после выполнения операций с использованием указателя кадра стека.
венные данные. Команда PUSHX обеспечивает загрузку в стек копии содержимого регистра X. С помощью команд PUSHT и PUSHS в стек помещаются соответственно копии верхнего и
PUSH #0002НPUSH #0002НРис. 5.27. Действие команд сРис. 5.27. Действие команд содним и двумя операндами.одним и двумя операндами.
PUSH #0002Н
PUSH #0002Н
Рис. 5.27. Действие команд с
Рис. 5.27. Действие команд с
одним и двумя операндами.
одним и двумя операндами.
следующего за ним( второго от верхушки) элементов стека. Команда PUSH offset (X) позволяет загружать в стек слова, расположенные недалеко от слова, на которое указывает регистр X, а команда POP offset (X) обеспечивает запись верхнего элемента стека в память по указанному адресу. Здесь смещение offset является 8-разрядным числом в дополнительном коде (от —128 до +127), которое с целью получения ис* полнительного адреса операнда прибавляется к содержимому
регистра X1). Выполнение некоторых операций с использованием регистра X показано на рис. 5.26.
Вторая группа команд, представленных в табл. 5.5, служит для выполнения арифметических и логических операций над данными, находящимися в верхушке стека. Как показано на рис. 5.27, команды с одним операндом позволяют изменять значение элемента в верхушке стека. С помощью команд с двумя операндами производятся операции над двумя верхним^ элементами стека, а результат помещается в стек вместо этих элементов. Такие команды называются безадресными, так как в них нс указывается адрес и операции всегда выполняются над. одним или двумя верхними элементами стека.
В состав третьей группы входят команды обслуживания ре-' гистров SP и X. Команды четвертой группы используют дл# обращения к элементам массива и другим данным косвенную адресацию, реализуемую с помощью указателей, значения которых вычисляются во время прогона программы. Если адрес переменной находится в верхушке стека, то с помощью команды VAL производится его замена на значение самой переменной. Команда STOW служит для выполнения операции инверсии. При этом слово данных, хранимое в верхушке стека, записывается в ячейку памяти, адрес которой содержится во втором слове стека, начиная с верхушки.
Все команды первых четырех групп приводят к установке триггера Z в 1, если результат их выполнения равен нулю, в противном случае Z устанавливается в 0. Команды BEQ и BNE, входящие в пятую группу так же, как и в процессоре Н6809, обеспечивают проверку содержимого триггера Z и выполнение перехода. Другие команды этой группы выполняются аналогично командам процессора Н6809.
Примеры программ
' На рис. 5.28 приведена программа для процессора НИ, обеспечивающая умножение двух чисел путем многократного повторения операции сложения. Содержимое стека в разных точках данной программы показано на рис. 5.29. В любой момент времени в стеке имеется не более пяти элементов. Хотя при каждом повторении цикла, по мере того как происходит
и В общем случае значения смещения offset должны быть четными, что является результатом использования для моделирования машины со стековой организацией PDP-11. Однако если бы мы разрабатывали такую машину сами, то могли бы или организовать доступ к отдельным байтам с помощью нечетных значений смещения, или путем умножения смещения на 2 увеличить число слов, обращение к которым производится с помощью команд, использующих индексирование.
заполнение и очистка стека, значение SP меняется, указатель «адра стека всегда содержит постоянный адрес, что позволяет производить обращение к переменным CNT и PROD.
Несмотря на серьезные трудности, связанные, например, с использованием пяти команд для уменьшения и проверки зна-
Адрес
Содержимое
Метка озерец
Операнд
Комментарий
ORG
2А4бН
Multiply MCND by MPY.
2А40
SPROD EQU
-2
Offset to PROD in stack frame.
2А40
SCNT EQU
-4
Offset to CNT.
2А40
SMCND EQU
-6‘
Offset to MCND.
2А40
*Assume
SP has already been loaded on entry.
2А40
24 •
START LDXS
Set up frame pointer.
2А41
04
PUSHT
Push a word onto the stack.
2А42
10
CLR
Clear it (initial PROD) .
2А43
01 2С00
PUSH
MPY
Set CNT equal to MPY.
2А46
01 2С02
PUSH
MCND
Make a copy of MCND in stacks
2А49
50 2А58
JMP
IN
Do the loop MPY times.
2А4С
02 FFFF
LOOP PUSH
#-1
Decrement CNT.
2A4F
14
ADD
2А50
08 FC
POP
SCNT (X)
2А52
04
PUSHT
Make a copy of MCND.
2А53
03 FE
PUSH
SPROD (X)
2А55
14
ADD
2А56
08 FE
POP
SPROD (X)
2А58
03 ЕС
INPUSH
SCNT (X)
Push a copy of CNT.
2А5А
40 F0
BNE
LOOP
Continue if not yet zero.
2А5С
29 Об
DONE ADD
SP,#6
Else clean up stack.
2А5Е
50 1000
JMP
1000H
Return to operating system,
2А61
*
PROD is at top of stack.
2А61
1
ORG
2C00H
2С00
05
MPYFCW
5
Multiplier value.
2С02
17
MOND FCW
23
Multiplicand value.
2С04
END
START
Рис. 5.28. Программа умножения, выполняемого путем многократного повторения операции сложения, составленная для процессора НИ.
чения CNT, программа для процессора НН все же несколько короче соответствующей программы для Н6809. Уменьшение длины программы можно объяснить эффективной организацией доступа к локальным переменным в стеке с помощью указателя кадра стека, а также использованием коротких 1-байто- вых команд. Однако при сравнении с соответствующей программой для процессора Н8000 можно заметить, что длина програм-
мы для процессора НН значительно больше. Это связано с тем, что в процессоре Н8000 можно намного эффективнее организовать доступ к локальным переменным, поскольку их помещают в регистры.
На рис. 5.30 представлена программа инициализации массива, предназначенная для процессора НИ. Эта программа иллюстрирует использование команд STOW, CMP и PUSHS. Читателям в качестве упражнения рекомендуется определить содержимое стека в процессе выполнения этой программы.
LOOP PUSH#-! ADDLOOP PUSH#-! ADDККSPSPPOP SGNT(X) PUSH!PUSH SPROD (X)POP SGNT(X) PUSH!PUSH SPROD (X)START LDXS PUSHT , DLR ' PUSH MPY PUSH MONDJMF JNSTART LDXS PUSHT , DLR ' PUSH MPY PUSH MONDJMF JNADDIN PUSH SCNT(X)POP SPROD (X) ВНЕ LOOPADDIN PUSH SCNT(X)POP SPROD (X) ВНЕ LOOP
LOOP PUSH#-! ADD
LOOP PUSH#-! ADD
К
К
SP
SP
POP SGNT(X) PUSH!
PUSH SPROD (X)
POP SGNT(X) PUSH!
PUSH SPROD (X)
START LDXS PUSHT , DLR ' PUSH MPY PUSH MOND
JMF JN
START LDXS PUSHT , DLR ' PUSH MPY PUSH MOND
JMF JN
ADDIN PUSH SCNT(X)
POP SPROD (X) ВНЕ LOOP
ADDIN PUSH SCNT(X)
POP SPROD (X) ВНЕ LOOP
Рис. 5.29. Содержимое стека после выполнения команд программы умножения.
Ад- Содер-метка рес жимое
КодОперанд Комментарий
о п е р а ц.
3000*
3000 02 3100 INIT
3003 05ILOOP
3004 04
3005 10
3006 31
3007 02 0002
300А 14
300В 02 ЗЮА
ЗСОЕ 16
300F 40 F2
3011 29 02
3013 50 1000
3016
3100 ??Q
ЗЮА
ORGЗОООН Initialize an array to 0.
Assume SP has already been loaded on entry.
PUSH IQBase address of array.
PUSHSGet a copy of array address.
PUSHTPush 0 (initial value).
CLR
STOWInitialize an array location.
PUSH|2Update array address.
ADD PUSHIQ+10Past end?
CMP BNEILOOPContinue if not.
ADDSP,#2Else clears up stack.
JMP1000HGo to operating system.
ORG3100H
RMW5Reserve 5 words.
ENDINIT
Рис. 5.30. Программа инициализации массива, составленная для процессор»
Преимущества и недостатки машин со стековой организацией
Машины со стековой организацией имеют большие преимущества при вычислении сложных выражений. Это можно показать на примере вычисления выражений, в которых используются умножение и деление. С помощью команды MUL процессора НИ производится умножение двух верхних слов стека, а полученный результат в виде одного слова помещается в стек вместо сомножителей. Если для записи произведения требуется более одного слова, то происходит потеря старших разрядов. Теперь предположим, что набор команд процессора Н8000 дополнен аналогичной командой (MUL reg2,opl), позволяющей умножать содержимое регистра на значение операнда, а результат в виде одного слова помещать в регистр. В табл. 5.6 приведены некоторые операторы присваивания языка Паскаль, использующие сложение и умножение, а также соответствующие этим операторам последовательности команд процессоров Н8000 и НИ. Хотя для вычисления каждого из выражений с помощью процессора НИ требуется большее число команд, чем при использовании процессора Н8000, отдельные команды для процессора НИ имеют меньшую длину, в результате чего программа в целом для него получается короче. Машины со стековой организацией могут иметь команды меньшей длины по сравнению с машинами с регистрами общего назначения, так как не требуют каких-либо разрядов для задания номеров регистров. Сравним, например, 3-байтовые команды PUSH и POP машины НИ с 4-байтовыми командами LD и ST процессора Н8000. Обе пары команд задают 16-разрядный адрес памяти, но в командах Н8000 используется дополнительный байт для выбора двух регистров общего назначения из шестнадцати.
Хотя программы машин со стековой организацией могут быть несколько короче, это не означает, что быстродействие таких машин выше, чем машин с регистрами общего назначения. Так как выполнение текущей команды обычно можно совместить с выборкой следующей команды, то быстродействие процессоров современных ЭВМ главным образом ограничивается быстродействием системы памяти. Поэтому для получения достаточно хорошей оценки относительной скорости реализации программы можно просто подсчитать число обращений к памяти (т. е. число считываний или записи), производимых в процессе выборки и выполнения команд.
В табл. 5.6 приведены данные о числе байтов памяти, к которым производится обращение при реализации программ вычисления выражений с помощью процессоров НИ и Н8000. Хотя программы процессора НИ по числу байтов несколько ко-
Таблица 5.6. Операторы присваивания и соответствующие им последовательности команд машин с регистрами общего назначения и стековой организацией
Оператор
Команды • маилины H8000
Команды маилины H11
Содержимое стека r
P:=Q*R+S+T
LD RO,Q (4,6)
PUSH Q
(3,5)
Q
MUL RO,R (4,6)
PUSH R
(3,5)
Q, R
ADD RO,S (4,6)
MUL
(1,7)
Q*R
ADD R0,T (4,6)
PUSH S
(3,5)
Q*R, S
ST RO,P (4,6)
ADD
(1,7)
Q*R+S
PUSH T
(3,5)
Q*R+S, T
(20,30)
ADD
POP P
(1,7)
(3,5)
(18,46)
Q*R+S+T
Стек пусгп
P: = (Q+R)
LD RO,Q (4,6)
PUSH Q
(3,5)
Q
*(S+T)
ADD R0,R (4,6)
PUSH R
(3,5)
Q, R
LD R1,S (4,6)
ADD
(1,7)
Q+R
ADD R1,T (4,6)
PUSH S
(3,5)
. Q+R, S
MUL R0,R1 (2,2)
PUSH T
(3,5)
Q+R, S,. T
ST R0,P (4,6)
ADD
(1,7)
Q+R, S+T
—
MUL
(1,7)
(Q+R) * (S+T)
(22,32)
POP P
(3,5)
(18,46)‘
Стек пуст
P: = (Q*R+S*T)
LD RO,Q (4,6)
PUSH Q
(3,5)
Q
* (U+V)
MUL R0,R (4,6)
PUSH R
(3,5)
Q, R
LD R1,S (4,6)
MUL
(1,7)
Q*R
ADD R1,T (4,6)
PUSH S
(3,5)
Q*R, S
ADD RO,R1 (2,2)
PUSH T
(3,5)
Q*R, S, T
LD R1,U (4,6)
MUL
(1,7)
Q*R, S*T
ADD R1,V (4,6)
ADD
(1,7)
Q*R+S*T
MUL R0,R1 (2,2)
PUSH U
(3,5)
Q*R+S*T, U
ST R0,P (4,6)
PUSH V
(3,5)
Q*R+S*T, U, V
—
ADD
(1,7)
Q*R+S*T, U+V
(32,44)
MUL
POP P
(1,7)
(3,5)
(26,70)
(Q*R+S*T) *(U+V)
Стек пуст
Примечания. В круглых скобках указаны длина команды в байтах и число байтов памяти, к которым производится обращение во время выборки и выполнения команд, В колонке «Содержимое стека» запятыми отделены элементы данных, загружаемы» в стек; элементы, записанные справа, находятся в верхушке стека.
роче, для их выполнения требуется значительно больше времени, чем для программ процессора Н8000. Это связано с тем, что единственным «рабочим регистром» НИ, используемым при выполнении арифметических операций, является верхушка сте-
ка, а стек находится в памяти, которая, как мы выяснили, определяет быстродействие системы.
Для устранения этого недостатка многие реальные машины со стековой организацией проектируются таким образом, чтобы программист мог помещать несколько верхних элементов стека в «сверхоперативные» регистры процессора. Когда программа обращается к верхушке стека, аппаратный доступ к памяти осуществляется только в том случае, если регистры стека процессора становятся заполненными или пустыми из-за превышения числа операций загрузки в стек над числом извлечений из стека или наоборот. Даже устранив таким способом упомянутый выше недостаток, разработчики ЭВМ до сих пор спорят, можно ли создать машину со стековой организацией памяти, обладающую таким же быстродействием, как и машина с регистрами общего назначения, при равных затратах на аппаратные средства и тех же усилиях технических работников.
Основное достоинство стеков заключается нс в том, что их удобно использовать при вычислении сложных выражений, а в том, что они являются наиболее естественной структурой для хранения адресов возврата и другой информации при вызове подпрограмм. Кроме того, как будет показано в гл. 9, их можно использовать для хранения параметров подпрограмм и локальных переменных в программах, написанных как на языке ассемблера, так и на языках высокого уровня. .
Для получения преимуществ, которые дают стеки при вызове подпрограмм, необязательно использовать машину с чисто стековой организацией. Так как стеки при работе с подпрограммами очень удобны, то почти во всех современных процессорах предусмотрены регистры и команды, которые можно использовать для операций над данными и адресами, хранимыми в стеке. '
ДРУГИЕ ТИПЫ ОРГАНИЗАЦИИ
ПРОЦЕССОРОВ И ИХ КЛАССИФИКАЦИЯ
Имеется лишь небольшое число реальных процессоров, которые можно вполне определенно отнести к разряду машин с аккумуляторами, регистрами общего назначения или стековой организацией. Так, в состав процессора 8086 входит восемь 16-р азрядных регистров, называемых в документации, выпущенной фирмой Intel, «регистрами общего назначения», — они используются при различных операциях аналогичным образом. Однако для выполнения умножения, деления и десятичных операций служит только один регистр (аккумулятор). Кроме того, при вычислении адресов все регистры используются по-разному. В данном случае цель классификации машин со-
стоит не в том, чтобы отнести машину к тому или иному классу, а в создании определенной системы, способствующей рассмотрению и пониманию различных архитектур процессоров.
Одним из критериев классификации является число адресов памяти, задаваемых в типичной команде. Рассмотрим общую форму записи команд с двумя операндами:
ОРЗ: = F(OP2,OP1);
Здесь F представляет собой функцию, такую, как сложение или умножение. Для процессора с аккумулятором эта форма записи имеет более простой вид:
А: = F(A,MEM1);
где А — содержимое аккумулятора, а МЕМ1 — операнд, находящийся в памяти. Команда задает только один адрес памяти— адрес операнда МЕМ1, поэтому ее называют одноадресной.
Для процессора с регистрами общего назначения команда с двумя операндами описывается следующим образом: REG:=F(REG,MEM1);’
Для задания операнда МЕМ1 здесь также используется полный адрес памяти, в то время как содержимое регистра REG задается с помощью короткого номера этого регистра. Поэтому такая команда носит название полутораадресной. Наборы команд всех процессоров с регистрами общего назначения обязательно включают такие команды. Эти команды также называют командами типа память-регистр (REG: = F(REG,МЕМ1); или регистр-память (MEM1: = F(MEM1,REG);).
Некоторые процессоры располагают также двухадресными командами, в которых оба операнда задаются с помощью адресов памяти:
МЕМ2 := F(MEM2,MEM1);
В машинах со стековой организацией оба операнда находятся в памяти, однако адреса этих операндов должны содержаться в верхней области стека:
PUSH(F(POP,POP));
Так как команда в данном случае не включает какого-либо адреса, ее называют безадресной.
Если в состав набора команд процессора входят преимущественно команды одного типа формата, то название процессора соответствует названию этих команд. Например, процессор Н6809 следует считать одноадресным. Однако некоторые процессоры имеют команды нескольких форматов. Так, мини-ЭВМ PDP-11 позволяет моделировать все рассмотренные форматы команд, а также ряд других, которые еще не были классифици-
рованы:
ADD
MEM1.REG
REG:= REG+MEM1;
ADD
REG,MEM1
MEM1 :=MEM1+REG;
ADD
MEM1,MEM2
MEM2 := MEM2+MEM1;
ADD
(SP)+,(SP)
PUSH(POP+POP);
ADD
(SP)+,REG
REG:= REG+POP;
ADD
(SP)+,MEM1
MEM1 := MEM1+PQP;
ADD
REG,(SP)
PUSH(POP+REG);
ADD
MEML(SP)
PUSH(POP+MEM1);
Архитектуре современных микропроцессоров присущи многие особенности. Одна из них состоит в том, что команды и данные хранятся в одном и том же запоминающем устройстве. Для большинства систем это необходимо, так как в них с помощью определенных средств разработки программ осуществляется обмен команд и данных. Например, загрузчик производит за' грузку программы, хранимой во внешнем запоминающем устройстве, в память и для этого должен интерпретировать ее как данные. Однако в широко распространенных приложениях, таких, как кассовые аппараты и системы автомобильного зажигания, средства разработки резидентных программ отсутствуют и программы никогда не смешиваются с данными. Поэтому в некоторых микроконтроллерах, например MCS-48, команды и данные хранятся в разных запоминающих устройствах; команды таких микроконтроллеров не позволяют производить обращение к ячейкам памяти, в которых хранится программа, как к ячейкам с данными.
Еще один интересный подход к проектированию процессоров связан с применением набора рабочих регистров. Вместо использования только одного набора регистров общего назначения в некоторых процессорах имеется несколько таких наборов. При этом для задания набора, с которым в данный момент производятся операции, служит указатель набора рабочих регистров. Например, процессор MCS-48 имеет два набора регистров, а процессор 9900 позволяет использовать в качестве таких регистров блок из 16 слов, расположенных в любой области адресуемой им памяти с пословной организацией емкостью 32К слов.
Кроме того, процессоры классифицируют по размеру обрабатываемых ими данных. Большинство современных процессоров позволяет адресовать и обрабатывать данные, минимальная длина которых равна 1 байт, некоторые могут производить адресацию даже отдельных разрядов регистров или памяти. Наибольший размер данных, обрабатываемых с помощью процессоров, обычно составляет 4 или 8 байт.
Если большая часть операций процессора производится над
операндами, максимальная длина которых составляет п разрядов, то процессор называется п-разрядным. Заметим, что такое название служит характеристикой регистров процессора и выполняемых им операций и не имеет отношения к структуре аппаратных средств, организации памяти или шине памяти. Поэтому процессор Н6809 следует назвать 8-разрядным, несмотря на то что некоторые его индексные регистры являются 16-разрядными и он может производить несколько операций над 16-разрядными данными; процессор 8086 является 16-раз- рядным, хотя его команды имеют длину от 1 до 7 байт; процессор 68 000, имеющий 16-разрядную шину данных памяти и 16-р азрядные внутренние шины передачи данных, тем не менее называется 32-р азрядным.
Процессоры также классифицируют по размеру и стоимости (разд. 1.7), по способам адресации и объему адресуемой памяти (гл. 7).
УПРАЖНЕНИЯ
Какова взаимосвязь, если она есть, между длиной команды и длиной обрабатываемых с ее помощью данных?
Какое число адресных разрядов требуется иметь в машинах с побайтовой и пословной адресациями, если объем их памяти составляет 131 072 слов? (Напоминаем, что 1 слово состоит из 2 байт.)
. Арифметическое сравнение двух величин можно осуществить, вычитая их друг из друга и определяя, является ли полученный результат меньше, или больше нуля, или равен нулю. Типичный процессор располагает командами для сравнения байтов и слов. Лексикографическая сортировка двух строк символов кода ASCII может быть выполнена путем их побайтового сравнения (начиная с крайних слева байтов), которое производится до тех пор, пока не будут обнаружены неодинаковые байты. Скажите, можно ли для увеличения скорости сортировки использовать команды, предназначенные для сравнения слов, если память имеет организацию, показанную на рис. 5.1,6 или в?
Команды десятичной арифметики служат для обработки последовательностей десятичных цифр. Объясните, как можно осуществить запись данных, обрабатываемых с помощью этих команд, в двоичную память, если организация памяти описывается на языке Паскаль следующим образом:
ТУРЕ
digit=0.,.9;
word=PACKED ARRAY [0..8] OF digit;
digit= 0..9;
VAR
MEM : ARRAY [address] OF word;
Почему в программе .на рис. 5.5. псевдокоманда FCB 23 языка ассемблера приводит к записи в память числа 17, а не числа 23?.
Быстродействие типичного процессора ограничено быстродействием памяти. Поэтому время выполнения любой команды в основном зависит от того, сколько раз следует произвести обращение к памяти для реализации циклов выборки и исполнения команды. В первом приближении общее время выполнения команды равно числу байтов памяти, к которым производится обращение (считывание и запись) с помощью данной команды, умноженному на
время одного обращения. Считая, что для одного обращения к памяти требуется 1 мкс, составьте таблицу времени выполнения всех команд процессора Н6809. Пример: команда SIX addr —выборка 3 байт команды, запись 2 байт, общее время выполнения 5 мкс. ^Примечание: для команд перехода считать, что выборка байта смещения offset производится даже в том случае» если переход не выполняется.)
Используя таблицу, полученную при выполнении упр. 5.6 (или реальные временные параметры микропроцессора М6809), определите время, выполнения одного внутреннего цикла программы, приведенной на рис. 5.6.
Используя таблицу, полученную при выполнении упр. 5.6 (или реальные временные параметры микропроцессора М6809), сравните время реализации программ, представленных на рис. 5.9 и 5.10. Составьте формулы, позволяющие рассчитать время выполнения обеих программ в зависимости от размера массива, подлежащего инициализации.
Предположим, что процессор Н6809 не имеет индексного регистра, а также команд для выполнения операций с этим регистром. Покажите, как следует переписать программу инициализации массива, представленную на рис. 5.10, используя команду, позволяющую модифицировать адресную часть (addr) другой команды. Новая программа по-прежнему должна обеспечивать инициализацию массива любого размера и иметь длину не больше, чем длина старой программы. Почему этот способ нельзя использовать на практике? 5.10. Предположим, что последняя команда подпрограммы процессора Н6809» выполняемая перед возвратом, должна вызвать другую подпрограмму. Покажите, что последовательность двух команд JSR addr и RTS всегда можно заменить одной командой JMP addr.
Если операционная система процессора Н6809 вызывает главную программу, которая представлена на рис. 5.14, с помощью команды JSR, то сможет ли главная программа осуществить возврат в операционную систему, используя команду RTS, а не JMP SYSRET?
Произведите ассемблирование приведенной ниже программы процессора Н6809 вручную. При этом необходимо получить листинг такого же формата, как на рис. 5.6. После этого добавьте комментарий, поясняющий, что выполняет данная программа.
ORGЗОООН
START LDXIV
CLRA STA Z
LOOP LDA @Х
ADDA' 2 STA Z ADDX #1 CMPX #V+LEN BNELOOP
LDAZ
RTS
ZRMB1
LENEQU10
VRMBLEN
ЕЮ START
Напишите для процессора H6809 подпрограмму преобразования 8-раз- рядного двоичного числа DECNUM в последовательность трех символов кода ASCII, являющихся цифрами соответствующего десятичного числа без знака. Считается, что при входе в подпрограмму входное число содержится в регистре А, а при выходе из подпрограммы результат должен находиться в 3 байт памяти ASC2 (старший байт), ASCI, ASC0.
Используйте следующий алгоритм:
^т,^1" 30Н; ASC1 := 30Н; ^о == 3°Н; (ascii 'О'}
WHILE DECNUM О О DO'
BEGIN
ASCO := ASCO + 1;
IF ASCO = ЗОН + И0 THEN BEGIN
ASCO := ЗОН; ASC1 := ASC1 + 1;
IF ASC1 = ЗОН + 10 THEN BEGIN '
ASC1 := ЗОН; ASC2 := ASC2 + 1
END;• '
END;
DECNUM := DECNUM - 1;
END;
Почему в поле mode команд процессора Н8000, имеющих формат 1, 2 или 3 (рис. 5.18), не может находиться число 112?
Почему для команды ST процессора Н8000 регистровый способ адреса* ции является запрещенным? Что можно сказать по этому поводу о командах JMP и CALL?
Перепишите программу, составленную для процессора Н8000 (рис. 5.19), таким образом, чтобы она служила для умножения слов, а не байтов.
Примерное время выполнения команд процессора Н8000 можно рассчитать, пользуясь способом, изложенным в упр. 5.6 для процессора Н6809. Однако 16-разрядные процессоры, такие, как Н8000, обычно имеют 16-разряд- ную шину данных памяти, что позволяет производить обращение сразу к полному слову. Считая, что время обращения к одному слову равно 1 мкс, составьте таблицу времени выполнения всех команд процессора Н8000 в зависимости от кода операции и способа адресации. Примеры: для выполнения команды LD reg2,opl требуется один цикл для выборки команды плюс дополнительные циклы для выборки операнда в соответствии с одним из способов адресации. В случае регистровой адресации дополнительных циклов не требуется; при прямой адресации необходимы два дополнительных цикла — один для выборки адреса, а другой для обращения к операнду.
Используя значения времени выполнения команд процессоров Н6809 и Н8000, полученные в упр. 5.6 и 5.17 соответственно, сравните время реализации внутренних циклов программ, представленных на рис. 5.6 и 5.19. Проведите аналогичное сравнение, используя реальные значения времени выполнения команд процессоров М6809 и Z8000.
Выполните предыдущее упражнение, считая, что процессор Н8000 имеет только 8-разрядную шину данных памяти и поэтому для обращения к слову, расположенному в памяти, требуется 2 мкс. ”
Покажите, как можно уменьшить время выполнения программы, представленной на рис. 5.19; помещая второй сомножитель М2 в регистр, и вычислите время реализации внутреннего цикла старой н новой программ.
Перепишите программу инициализации массива (рис. 5.20), используя для контроля числа циклов команду DJNZ вместо команды СМР. Сравните длину старой и новой программ, а также время их выполнения.
Программа процессора Н8000, приведенная на рис. 5.21, может содержать меньшее число выполняемых команд, чем соответствующая программа процессора Н6809 на рис. 5.14. Однако для нее требуется большее число строк комментария. Объясните, почему. -
Проведите ассемблирование приведенной ниже подпрограммы процессора Н8000 вручную. При этом необходимо получить листинг такого же формата, как на рис. 5.19. Напишите также комментарий, поясняющий, что выполняет данная подпрограмма.
ORG
3000H
START
LD
R1,#V
CLR
RO
LOOP
ADD
RO,@R1
ADD
RI,#2
CMP
R1,#V+LEN
BR
NE,LOOP
RET
LEN
EQU
20
V
. RMB
LEN
END
START
5.24. Выполните
ynp. 5.13 для процессора H8000, используя 16-разряднее
входное число. Полученное выходное число должно состоять из 5 цифр.
Составьте программу на языке Паскаль, моделирующую работу процессора Н8000.
Используя структуру программ, представленных на рис. 5.14 и 5.21, напишите для процессора НИ главную программу и подпрограммы подсчета единиц. Величины, которые передаются в подпрограммы и из подпрограмм, должны содержаться в стеке, а не в определенных ячейках памяти. Чтобы «меть возможность отдельно производить обработку старшего и младшего байтов слова, используйте команду SWAB. Ассемблирования программы выполнять не требуется.
Проведите ассемблирование приведенной ниже подпрограммы процессора НН вручную. При этом необходимо получить листинг такого же формата, как на рис. 5.28. Кроме того, напишите комментарий, поясняющий, что выполняет данная подпрограмма.
ORG
3000H
VSUM
LDXS
PUSH
PUSHT
#v
CLR
PUSH
VADDR (X)
COOP
VAL
ADD
PUSH
VADDR (X)
PUSH ADD
PUSHT
#2
POP
VADPR (X)
PUSH
CMP
#V+LEN
BNE
LOOP
PUSH
RADDR (X)
POP
VADDR (X)
PUSH
SUM (X)
POP
RADDR (X)
ADD RTS
SP,/4
:RADDR
EQU
0
VADDR
EQU
-2
SUM
EQU
-4
LEN
EQU
20
V
RMB
LEN
END
VSUM
Составьте программу на языке Паскаль, моделирующую работу процессора НИ.
Проведите классификацию всех процессоров, рассмотренных в разд. 5.2^ 5.4, исходя из следующих критериев: основной длины операндов, над которыми производятся операции (8, 16 или 32 разряда), и числа адресов, заданных в одной команде (0, 1, 1,5 или 2).
ЛИТЕРАТУРА
Наилучший способ изучения организации ЭВМ состоит в ознакомлении? с архитектурой реальных машин. Из процессоров, рассматриваемых в данной книге, наиболее подходящим примером устройства, в котором используется аккумулятор, является процессор 6809. Процессоры PDP-11, Z8000 и 68000 служат хорошим образцом машин с регистрами общего назначения, каждая из» которых обладает своими архитектурными особенностями, представляющими интерес для изучения. Хотя здесь не описывается ни одной машины с чистостековой организацией, представление о такой машине можно получить, если ознакомиться с мини-ЭВМ PDP-11 (гл. 13). Наиболее распространенными среди средних и малых ЭВМ с чисто стековой архитектурой являются машины НР3000 и НР300. - *
Общей организации ЭВМ и используемым аппаратным средствам посвящено несколько книг, в том числе: Hill, Peterson, Digital Systems: Hardware Organization and Design, Wiley, 1980, 3-rd ed.; Hamacher, Vranesic, Zaky, Computer Organization, McGraw-Hill, 1978 и Hayes J., Computer Architecture and Organization, McGraw-Hill, 1978. К наилучшим работам в области классификации ЭВМ следует отнести следующие книги: Bell, Newell, Computer Structures: Readings and Examples, McGraw-Hill, 1971 и Bell, Newell, Siewiorek, Principles of Computer Structures, McGraw-Hill, 1980.
Многие интересные решения, воплощенные в организации некоторых моделей вычислительных машин фирмы DEC (в том числе PDP-11), нашли отражение в книге Bell, Mudge, McNamara, Computer Engineering, Digital Press, 1978. Проектировщики созданных сравнительно недавно микропроцессоров предприняли попытку объяснить организацию разработанных ими процессоров, а также некоторые технические и экономические соображения, которыми они руководствовались при этом. Так, микропроцессоры Z8000 и 68000 описаны соответственно в статьях Peuto В., Architecture of a New Microprocessor и Stritter S., Gunter T., A Microprocessor Architecture for a Changing World, опубликованных в февральском номере журнала Computer, 12, 2 (1979). Авторы статьи Ritter Т., Boney J., A Microprocessor for the Revolution: The 6809 \Byte, 4, 1, 2, 3 (Jan., Feb., Mar. 1979)] рассмотрели «самую лучшую 8-разрядную машину, когда-либо созданную человеком». В журнале Computer, 11, 6 (1978) содержится статья Morse, Pohlman, Ravenel, The Intel 8086 Microprocessor; A 16-bit Evolution of the 8080, в которой нашла отражение история создания микропроцессора 8086.
66
6
6
ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ АССЕМБЛЕРА
Машинный язык и язык ассемблера не одно и то же. Машинный язык вычислительной машины представляет собой набор последовательностей (цепочек) битов, которые воспринимаются машиной как команды; действия, выполняемые каждой машинной командой, определяются электронными схемами вычислительной машины. Язык ассемблера — это символический язык, являющийся программным инструментом. Программы, написанные на этом языке, могут непосредственно транслироваться на машинный язык с помощью системной программы, называемой ассемблером. Результатом работы программы ассемблера является объектный модуль, содержащий цепочки битов (совокупность которых составляет программу на машинном языке), а также информацию, которая указывает программе загрузчика, в какую область памяти вычислительной машины поместить эти цепочки битов.
Процесс разработки программы с использованием языка ассемблера представлен на рис. 6.1. Обычно программист использует редактор текста, который формирует тестовый файл, содержащий программу на языке ассемблера. Названия исходный файл и исходная программа часто используются для текстовых файлов, представленных на языке ассемблера и языках программирования высокого уровня. Ассемблер воспринимает исходную программу в качестве входной информации, проверяет корректность форматов и формирует объектный модуль, содержащий программу на машинном языке. Загрузчик произво- дйт загрузку объектного модуля в память вычислительной машины. Впоследствии на этой машине выполняется прогон программы, в случае необходимости — с помощью отладчика.
Прогон загрузчика, отладчика и программы на машинном «языке должен производиться на вычислительной машине пользователя; редактор текста и ассемблер могут выполняться как •на этой, так и на какой-либо другой вычислительной машине. Ассемблер, который выполняется на одной ЭВМ и формирует
объектные модули для другой, называется кросс-ассемблером. Например, существует возможность сформировать программу на языке ассемблера, используя «интеллектуальный» терминал с запоминающим устройством на магнитных лентах, затем переписать исходный файл на магнитной ленте в память большой ЭВМ. В дальнейшем эта ЭВМ производит ерогон кросс-ассемб-
Рис. -6.1. Этапы разработки программы на языке ассемблера.
лера и «перезагружает» объектный модуль в память местной микро-ЭВМ, используя канал последовательной передачи данных.
ЯЗЫК АССЕМБЛЕРА
Несмотря на то что фирмы-изготовители ЭВМ определяют Для каждой выпускаемой ими машины стандартный алгоритмический язык ассемблера, у потребителей этих ЭВМ может возникнуть необходимость в использовании других языков ассемблера. Хотя действие каждой команды машинного языка реализуется с помощью технических средств, пользователь, определяющий язык ассемблера, имеет возможность задать следующие параметры:.
мнемоническое обозначение каждой команды машинного языка;
стандартный формат для строк программы, представляемой на языке ассемблера;
• формат для указания различных способов адресации и вариантов команд;
формат для указания символьных констант и констант целочисленного типа систем счисления с различным основанием;
способы задания соответствия символьных имен адресам и другим числовым значениям;
способы определения данных при хранении их в памяти совместно с командами при загрузке программы;
псевдокоманды, указывающие последовательность выполнения трансляции (ассемблирования) программы.
Обычно все языки ассемблера для данной ЭВМ имеют одинаковые мнемонические обозначения команд и указатели способов адресации, но могут различаться форматом строк, максимальной размерностью идентификаторов, форматом констант, псевдокомандами. Хотя для данной ЭВМ может быть разработано множество различных языков ассемблера, практическое использование имеют только несколько из них — это обычно стандартный алгоритмический язык изготовителя ЭВМ и одна или две версии ассемблера, представленные другими разработчиками программного обеспечения.
Таким образом, для каждой ЭВМ имеется стандартный алгоритмический язык ассемблера, однако единый стандарт, которого бы . придерживались все разработчики программного обеспечения, отсутствует. В этой главе мы опишем сравнительно простой язык ассемблера для микро-ЭВМ Н6809. Подобно самой микро-ЭВМ Н6809, язык ассемблера для этой машины носит гипотетический характер. В части III будут рассмотрены стандартные языки ассемблера для реальных вычислительных машин.
Форматы алгоритмического языка ассемблера
Примеры операторов и программ, написанных на языке ассемблера, были приведены в гл. 5. В отличие от программ на языке Паскаль программы на языке ассемблера обычно организованы построчно, при этом каждый оператор языка ассемблера находится в отдельной строке в соответствии с установленным форматом. Каждая строка программы содержит следующие четыре поля:
LABEL OPCODE OPERAND COMMENTS
Поле LABEL (Метка) не является обязательным. Метка — это обычный идентификатор (или мнемоническое обозначение & языке ассемблера), представляющий собой последовательность букв и цифр, начинающуюся с буквы. Максимальная длина поля метки различна для разных ассемблеров; большинство' полей имеет длину не менее шести символов. Подобно некоторым компиляторам с языка Паскаль, многие ассемблеры допускают использование полей меток произвольной длины, но распознают только первые шесть или восемь символов.
При определении каждому мнемоническому обозначению в программе на языке ассемблера присваивается значение. Программа ассемблера следит за метками и их значениями с по-
мощью внутренней таблицы символов или имен. Например, в приведенном ниже операторе значение метки START есть адрес памяти, по которому хранится команда LDA:
START LDA XX Загрузить А содержимым ячейки памяти XX
Обычно значением метки является адрес памяти, по которому хранится соответствующая команда или элемент данных; исключение составляет псевдооперация EQU, которая будет описана ниже. Каждая метка может быть определена только один раз, но ссылка к ней может организовываться столько раз, сколько это необходимо.
Поле OPCODE (Код операции) содержит мнемоническое обозначение либо машинной команды, либо псевдооперации. В зависимости от содержимого поля OPCODE поле OPERAND (Операнд) указывает либо несколько операндов, разделенных запятыми, либо не содержит ни одного операнда. Операнд представляет собой выражение, содержащее мнемонические обозначения, константы и операторы, такие, как + и —. Простейшее. выражение содержит одно мнемоническое обозначение или одну константу.
В языке ассемблера микро-ЭВМ Н6809 для указания десятичной константы используется последовательность десятичных цифр, а для указания шестнадцатеричной константы — последовательность шестнадцатеричных цифр, за которыми следует буква Н. Чтобы отличать константы от мнемонических обозначений, в старший разряд константы записывают нуль (например, константа 0F123H). Символьные константы выделяются кавычками (например, ’А’) и имеют соответствующее значение в Стандартном американском коде для обмена информацией (ASCII). В других языках ассемблера для обозначения констант могут использоваться префиксы или суффиксы.
.Шестнадцатеричная система счисления наилучшим образом подходит для представления данных и адресов в 8-, 16- и 32-разрядных ЭВМ. Байтовые значения удобно выражаются двумя символами, а 16- и 32-разрядные величины легко поделить на байты. Гибкость этой системы становится особенно очевидной, если вспомнить о тех трудностях, которые возникают ври представлении величин в десятичной или даже восьмеричной системе счисления, например какое значение слова получается .при конкатенации (сцеплении) байтов 235s, 3128?
Поле COMMENTS (Комментарии) игнорируется ассемблером, однако оно имеет важное значение для выполнения процесса программирования. Это поле должно содержать высокоуровневое описание алгоритма и структур данных, используе- ыых в программе. Все программы на языке ассемблера, приведенные в данной книге,. комментируются либо предложениями
и фразами, написанными на естественном языке, либо соответствующими операторами алгоритмического языка Паскаль.
Во многих языках ассемблера, включая язык микро-ЭВМ Н6809, применяются строки фиксированного формата, использование которого налагает следующие ограничения:
поля отделяются друг от друга одним или несколькими пробелами;'
метка, если она присутствует, начинается с первой колонки (позиции) « троки. Если поле метки пустое, то строка должна начинаться хотя бы с одного пробела;
между операндами ставят запятые (или используют другие разделители); использование пробелов в поле операнда запрещается;
поле комментария начинается с пробела, следующего за последним операндом;
если строка полностью отведена для комментария, она начинается с символа звездочка (*).
Языки ассемблера, в которых используется свободный формат строк, являются более гибкими:
метка, содержащая идентификатор, заканчивается двоеточием; в начале каждой строки может либо отсутствовать какая-либо метка, либо присутствовать одна или несколько меток;
как и в языке Паскаль, программисту разрешено использование пробелов для разделения полей и операндов;
поля можно и не разделять пробелами; исключение составляют поля OPCODE и OPERAND, использование пробелов между которыми исключает совместное использование идентификаторов;
комментарий начинается специальным символом, например «;».
Приведем пример фрагмента программы на языке ассемблера со свободным форматом:
START : FIRST : MOV @R0,Y; Эти две строки действительно компактны SUB QQ,ZEB
;Остальные строки выглядят обычным образом *-
NEXT:
ADD
ZEB,У;
Удобно выравнивать поля...
MOV
@R1,ZEB;
Где поместить символ «;» зависит от ваше
BEQ
FIRST;
го вкуса, тем не менее будьте последова-
тельны!
Псевдооперации и счетчик ячеек программы
Программа, приведенная на рис. 6.2, содержит как машинные команды, так и псевдокоманды. Псевдокоманды указывают ассемблеру порядок трансляции исходной программы и могут
,D„Add the first 15 integers.
START
„rD. 20°°H start assembly at location 2000 hex.
Clear SUM. STASUM
ALOOP
LDAICNTGet initialvaluefor CNT.
STACNTSave value ofCNT.
ADDASUMAdd to SUM.
STASUM
LDACNTUpdate CNT.
ADDA#-1
SYSA CNT SUM IVAL
I CNT
BNEALOOPContinue if CNT not zero yet.
EOUinnnu^tur"to operating system when done,
EQU000HOperating system return address.
JReservestorage forCNT.
№1Reservestorage forSUM.
E$uI5Initialvalue is 15.
FCBIVALStore intial value.
ENDSTART
Рис. 6.2. Программа на языке ассемблера для микро-ЭВМ Н6809.
LN# PLC CONTENTS
LABEL
OPCODE OPERAND
COMMENTS
001 0000
€02 0000
*
ORG
2000H
Add the first 15 integers. Start at location 2000 hex.
€03 2000 4F
START
CLRA
Clear SUM.
€04 2001 B7 201B
STA
SUM
€05 2004 Вб 201C
LDA
ICNT
Get initial value for CNT.
006 2007 B7 201A
ALOOP
STA
CNT
Save value of CNT.
007 200A BB 201B
ADDA
SUM
Add to SUM.
008 200D B7 201B
009 2010 Вб 201A
STA
LDA
SUM CNT
Update CNT.
010 2013 8B FF
ADDA
#-1
Continue if CNT not zero ye
011 2015 26 F0
BNE
ALOOF
012 2017 7E 1000
JMP
SYSA
Return to system when done.
013 201A
SYSA
EQU
1000H
System return address.
014 201A
CNT
RMB
1
Reserve storage for CNT.
015 201B
SUM
RMB
1
Reserve storage for SUM.
016 201C
IVAL
EQU
15
Initial value is 15.
017 201C OF
ICNT
FCB
IVAL
Store intial value.
018 201D
END
START
SYMBOL TABLE:
SYMBOL VALUE
SYMBOL
' VALUE
SYMBOL VALUESYMBOL VALUE
START 2000
ALOOP
2007
SYSA
1000CNT201A
SUM201В
IVAL
000F
ICNT
201C
Рис. 6.3. Листинг, выдаваемый ассемблером для транслируемой программы
Таблица 6.1. Псевдокоманды языка ассемблера
Выполняемая псевдооперация
Формат
Описание
Начальная установка
label ORG ехрг
Enter (label,PLC);
PLC : = expr;
Присвоение значения
label EQU ехрг
Enter (label,expr);
Формирование константы количества байтов
label FOB ехрг
Enter (label,PLC);
MEM[PLC] : =
expr MOD 256;
PLC:= PLC+1;
Формирование константы количества слов
label FCW ехрг
Enter (label,PLC);
MEM[PLC]: = expr DIV 256;
MEM[PLC+1]: =
expr MOD 256;
PLC: = PLC+2;
Резервирование в памяти количества байтов
label RMB expr
Enter (label,PLC);
PLC := PLC+expr;
Резервирование в памяти количества слов
label RMW expr
Enter (label,PLC);
PLC := PLC+2* expr;
Завершение работы ассемблера
label END expr
Enter (label, PLC);
StartingAddr := expr;
EndAssembly;
Примечания. 1) Параметр ехрг указывает любое допустимое выражение;
2) МЕМ |[х] загружается в память во время загрузки; 3) оператор Enter(label,expr) обеспечивает ввод метки (label) в таблицу символов со значением, равным значению (ехрг).
при необходимости обеспечивать формирование команд или данных. В табл. 6.1 описывается действие каждой псевдокоманды.
Ассемблер, который обрабатывает программу, представленную на рис. 6.2, формирует листинг, который приведен на рис. 6.3. Производится автоматическая нумерация строк текстового файла для облегчения организации ссылок и их корректировки. Для того чтобы следить за тем, где в памяти должны быть записаны транслируемые команды и данные, ассемблер использует внутреннюю переменную, называемую счетчиком ячеек программы (PLC). Вторая колонка на рис. 6.3 содержит значения счетчика ячеек программы перед ассемблированием каждой строки.
Счетчик ячеек программы устанавливается" в начальное состояние с помощью псевдооперации ORG. Содержимое счетчика обычно корректируется после обработки каждой строки транслируемой программы путем прибавления длины команды или данных, прошедших трансляцию. Для организации ссылки к текущему значению счетчика ячеек программы в выражениях используется специальный символ, например звездочка.
Во время трансляции команд значение счетчика ячеек про-* граммы соответствует величине, которую имеет счетчик команд (PC) при выполнении программы. Однако при выполнении программы значение счетчика ячеек программы не всегда совпадает со значением счетчика команд. Например, при трансляции констант данных счетчик ячеек программы принимает такие значения, которые счетчик команд иметь не может, если только не возникнет ситуации, когда программа попытается манипулировать своими данными как командами.
Ассемблер следит за обозначениями, которые определяет пользователь, с помощью таблицы символов. Обычно когда идентификатор появляется в поле LABEL, он сразу же вводится в таблицу символов вместе с текущим значением счетчика ячеек программы. Обратите внимание на то, что многие метки используются до того, как они определяются; подобный способ носит название обработки метки со ссылкой вперед. Ассемблер допускает ссылки вперед благодаря тому, что трансляция программы выполняется в два прохода. Во время первого прохода формируется таблица символов, тогда как во время второго прохода выполняется трансляция команд. (Этот процесс будет подробно описан в следующем разделе.)
Псевдооперация EQU является единственной операцией, которая не присваивает текущего значения счетчика ячеек программы своей метке, в данном случае используется значение операнда псевдооперации EQU. Следовательно, псевдооперацию EQU можно использовать для присвоения константы какой- либо метке, подобно определению CONST в языке Паскаль. Можно написать программу и не прибегая к псевдооперации EQU, однако в этом случае программы менее понятны и возрастают сложности при их отладке. На рис. 6.3 показаны два различных варианта реализации псевдооперации EQU; в строке 13 псевдооперация EQU используется для определения адреса, в строке 16 эта псевдооперация служит для определения значения константы.
Важно отметить, что псевдооперация EQU непосредственно действует на таблицу символов, формируемую во время процесса трансляции. Эта псевдооперация не обеспечивает хранения каких-либо величин в памяти, когда производится загрузка программы. Следовательно, значение счетчика ячеек программы не изменяется при выполнении псевдоопераций EQU^ приведенных на рис. 6.3. .
Значения констант запоминаются в памяти с помощью псевдоопераций FCB и FCW, которые осуществляют вычисление операндов во время трансляции и запись в память результирующих значений байтов или слов при загрузке программы. При выполнении псевдоопераций FCB и FCW может участво-
вать большое число операндов, разделенных запятыми, причем значения этих операндов запоминаются в последовательно расположенных ячейках памяти.
Иногда обучающиеся не улавливают различия между псевдооперацией EQU и псевдооперациями FCB/FCW, а этот момент является чрезвычайно важным. Оператор EQU определяет константу во время процесса трансляции, устанавливая связь между меткой и числом, которая действительна только во время процесса трансляции. Всякий раз, когда ассемблер встречает оператор EQU, он заменяет его на соответствующее числовое значение, как если бы это число было напечатано.
Таким образом, оператор
IVAL EQU 15
дает возможность программисту записать следующий оператор:
LDA #IVAL
вместо
LDA . #15
Если программист захочет изменить начальное значение параметра IVAL, то достаточно внести изменения в оператор EQU, определяющий значение IVAL. В этом случае программисту не требуется просматривать всю программу в поисках всех команд LDA=H=15. Действительно, последняя процедура требует особой осторожности, так как число 15 может использоваться и для других целей, не имеющих отношения к оператору IVAL.
С помощью псевдооперации FCB или FCW определяют одну или несколько констант для процесса выполнения: числа, которые записываются в память ЭВМ при загрузке программы; числа, которые предоставляются процессору при выполнении программы. Таким образом, оператор
ICNT FCB 15
назначает ячейку памяти, в которой запоминается число 15 при загрузке программы. В таблице символов ассемблера значение метки ICNT является не числом 15, а адресом, по которому хранится это число. Следовательно, программа может загрузить число 15 в А с помощью оператора
LDA ICNT
Продолжим рассмотрение других псевдоопераций. Псевдооперация RMB изменяет значение счетчика ячеек программы с целью зарезервировать один или несколько байтов памяти для хранения переменных без начальной установки байтов при загрузке программы. Это является полной аналогией объявления
переменной на языке Паскаль, где значения переменных в программе или процедурном блоке не определяются при вводе блока. Псевдооперация RMW аналогична псевдооперации RMB и отличается лишь тем, что резервирует указанное количество машинных слов.
В отличие от объявлений CONST и VAR в языке Паскаль псевдооперации EQU, FCB, FCW, RMB и RMW могут присутствовать в середине или в конце программы, представленной на языке ассемблера. Однако псевдооперации, обеспечивающие запись в память констант или резервирующие память, не должны использоваться в середине последовательности выполняемых команд. Рассмотрим, например, следующий фрагмент программы:
LDA SUM
SUM RMB 1
ADDA NEXT
После выполнения команды LDA вычислительная машина воспринимает любое значение, записанное в ячейке с именем SUM, как команду, что может привести к некорректному выполнению программы вследствие непредсказуемой операции.
Псевдооперация END указывает конец программы на языке ассемблера, за которой при необходимости может указываться адрес первой выполняемой команды, иначе говоря, «начальный адрес» программы. Эта информация может использоваться загрузчиком для автоматического выполнения программы после ее загрузки в память машины.
Выражения
Поле операнда оператора на языке ассемблера может содержать выражение, причем допустимые выражения зависят от конкретного языка ассемблера. Следующими допустимыми выражениями для любого ассемблера являются: значение константы; текущее значение счетчика ячеек программы (например, *); предварительно определенное мнемоническое обозначение; два или более элементов, указанных выше и соединенных знаками арифметических операций «+» и «—».
Некоторые ассемблеры обрабатывают такие операции, как умножение, деление, логические операции и операции сдвига.. При этом в отличие от алгоритмических языков высокого уровня не допускается использование круглых скобок или указание последовательности выполнения операций. Выражения обрабатываются ассемблером строго слева направо. Например, выражение P+Q*R выполняется как (P+Q)*R, а не как P+(Q*R)». что следовало бы ожидать, если бы данное выражение было
представлено на языке Паскаль. Для избежания неоднозначности в такой ситуации это выражение лучше всего записать следующим образом: Q*R+P, чтобы в любом случае получить один и тот же результат.
Существует еще одно важное различие между выражениями на языке ассемблера и выражениями на алгоритмических языках высокого уровня. Выражение операнда, задаваемое в операторе языка высокого уровня, вычисляется во время прогона программы, а выражение операнда, задаваемое в операторе языка ассемблера, определяется во время трансляции. Сравните два следующих оператора:
А := XX;{Присвоить значение переменной XX регисту А}
LDA XXЗагрузить в регистр А содержимое ячейки памя
ти XX
Очевидно, что действие обоих операторов идентично, если адрес переменной эквивалентен ее имени. Рассмотрим, что произойдет, если операнд не является простой переменной:
А:=ХХ +1; {Присвоить значение 14-переменная XX регистру А}_
LDA . XX4-1 Загрузить в регистр А содержимое ячейки памяти ХХ4-1
В операторе языка высокого уровня значение переменной XX считывается на этапе прогона программы, а затем добавляется единица. Выражение операнда на языке ассемблера вычисляется при трансляции программы, а не во время ее выполнения. Действие оператора LDA XX-4-1 состоит в том, чтобы загрузить в регистр А содержимое ячейки памяти XX-J-1, которое не имеет никакого отношения к содержимому ячейки памяти XX.
Операции трансляции, загрузки и прогона
Для начинающих программистов, занимающихся разработкой программ на языке ассемблера, весьма важно четко понимать различия между операциями трансляции, загрузки и протона.
Операции трансляции исходного модуля выполняются про- траммой ассемблера однократно, при этом исходный модуль должен быть представлен на языке ассемблера. Выражения •операндов всегда вычисляются во время процесса трансляции. Когда встречаются операторы «+» и «—» помните, что операции сложения и вычитания выполняются на этапе трансляции, а не при прогоне программы.
Операции загрузки реализуются с помощью программы-загрузчика однократно при загрузке объектной программы в память вычислительной машины. Все команды и константы, полученные при выполнении псевдоопераций FCB и FCW, записываются в память во время загрузки программы.
Операции прогона выполняются программой, представленной на машинном языке и загруженной в память данной вычислительной машины. В каждый момент времени обрабатываются соответствующие команды. Например, команда ADD выполняет операцию сложения во время прогона программы.
Употребление жаргона программистами, занятыми разработкой программ на языке ассемблера, стирает различия между операциями, выполняемыми во время трансляции и прогона. Например, что касается ассемблера, то такое обозначение, как CNT (см. рис. 6.3), эквивалентно его значению 201А в таблице символов. Однако когда программисты ведут речь об операциях во время прогона программы, они часто используют мнемоническое обозначение, чтобы соотнести его с содержимым ячейки памяти по соответствующему адресу. Таким образом,, программа может содержать комментарий: CNT: = 15 или Адрес(СМТ) =201А, когда в действительности следовало бы написать: MEM[CNT]: = 15 или CNT=201A. Смысл такого комментария обычно понятен из контекста.
Подобно командам, все значения данных, формируемые псевдооперациями FCB и FCW, загружаются в память программой-загрузчиком однократно во время процесса загрузки» Обычно принято установку в начальное состояние констант,, относящихся ко времени прогона программы, осуществлять на этапе загрузки объектной программы, как это сделано в отношении константы ICNT (см. программу на рис. 6.3). Однако программист должен избегать установки в начальное состояние переменных во время загрузки. Например, изменив строку 15 в указанной программе на следующую:
SUM FCB 0
казалось бы, устраняется необходимость для команд в строках 3 и 4 устанавливать величину SUM в нуль. Это было бы верно, если бы программа исполнялась только один раз. Однако в случае, когда программа должна быть повторно выполнена по адресу 2000Н без ее перезагрузки, величина SUM будет иметь значение, сформированное программой во время первого прогона и не равное нулю. По этой причине все переменные необходимо устанавливать в начальное состояние во время прогона программы.
Программа, прогон которой может быть остановлен в любой произвольной точке и немедленно возобновлен с начальной, точ-
ки без появления в ней ошибок, называется повторно последовательно используемой программой. Соблюдение одного простого правила гарантирует, что программа может повторно использоваться с самого начала:
Ни одна команда не должна модифицировать другую команду или содержимое любой другой ячейки памяти, которая была установлена в начальное состояние во время процесса загрузки..
В частности, ячейки памяти, установленные в исходное состояние псевдооперацией FCB или другими аналогичными псевдооперациями, не должны модифицироваться программой.
Програмы, которые сами себя модифицируют, называются самомодифицирующимися программами. Самомодифицирующий код был необходим для вычислительных машин ранних поколений, поскольку в этих машинах не предусматривалось возможности косвенной адресации. Единственным способом обращения к ячейке памяти, адрес которой вычисляется при прого- «е, было «включение» этого адреса в команду. Однако в современных вычислительных машинах нет никакой необходимости в использовании самомодифицирующего кода из-за низкой степени его структурированности и трудностей, возникающих при отладке. Указанные факторы чреваты неприятностями для программиста, особенно в тех случаях, если его программу ■попытаются выполнить с использованием постоянного запоминающего устройства.
-6.2. ДВУХПРОХОДНЫЙ АССЕМБЛЕР
Язык ассемблера предоставляет возможность производить •ссылки вперед, . при этом можно осуществлять обращение -к идентификатору до того, как он будет определен. Рассмотрим
•следующий пример:
dNITN
RMB
1
-
LDA
INITN
Получить начальное значение'•
BNE
NEXT
Верно, если значение не равно нулю
LDA
*10
Если нуль, использовать значение по умолчанию•.
NEXT
STA
CNT
Установить счетчик в начальное состояние
CNT
RMB
1
Здесь машинная команда для оператора LDA INITN может ^быть сформирована сразу же, так как адрес INITN уже определен. Однако операторы STA CNT и BNE NEXT содержат
ссылки вперед и определяются в программе позже. Переместив определение оператора CNT в начало программы, можно ликвидировать одну ссылку вперед, однако использование подобного приема для другой ссылки невозможно.
Программа двухпроходного ассемблера реализует ссылки вперед путем двухразового считывания исходного файла. При первом проходе производится просмотр каждой строки в поисках определений идентификаторов, которые используются для формирования таблицы символов. При втором проходе строки просматриваются вновь, но на этот раз формируются соответствующие команды на машинном языке и значения данных, которые размещаются в объектном модуле (при необходимости выдается листинг объектного модуля). Поскольку все метки определены в таблице символов к концу первого прохода, то символьные ссылки могут быть разрешены во втором проходе путем просмотра таблицы символов. .
Для того чтобы сформировать таблицу символов, двухпроходный ассемблер должен корректно обновлять содержимое счетчика ячеек программы после считывания каждой строки исходной программы при первом проходе. Если исходная строка содержит машинную команду, то ассемблер должен определить длину команды, хотя значение ее операнда (операндов) может быть еще неизвестно. Длина команды обычно определяется только по мнемоническому обозначению кода операции; в некоторых случаях необходимо также проверять указатель способа адресации. Например, команда CLRA микро-ЭВМ Н6809 имеет длину только 1 байт, в то время как длина команды LDA addr всегда равна 3 байт независимо от значения адреса (addr). Длина команды LD reg2,opl микро-ЭВМ Н8000 составляет 2 байт, если opl указывает регистр общего назначения или регистр косвенной адресации; в противном случае эта команда имеет длину 4 байт.
Если исходная строка содержит псевдооперацию, которая влияет на значение счетчика ячеек программы, то ассемблер должен определить это воздействие на счетчик ячеек программы сразу же, как только подобная строка обнаруживается в первый раз. Например, при обнаружении псевдооперации FCB содержимое счетчика ячеек программы (PLC) должно быть увеличено на число операндов, т. е. на число формируемых значений байтов памяти. При первом проходе количество операндов известно, хотя их значения могут отсутствовать. В то же время операнды таких команд, как ORG, RMB и RMW, должны быть
определены к моменту, когда псевдооперация встречается впервые при выполнении первого прохода, поскольку содержимое счетчика ячеек программы зависит от значения операнда.
Хотя псевдооперация EQU нс влияет на содержимое счетчика ячеек программы, се операнды обычно не должны содержать ссылок вперед. «Удачные» ассемблеры допускают один уровень ссылок вперед (рис. 6.4). Другие ассемблеры могут форми-
One level of forward referencing with EQU. This is acceptable to some assemblers.
START
.. .
LEN
EQU
LAST-START
LAST is undefined at this point in
*
' Pass 1, and therefore so is LEN.
LENGTH
FCW
LEN
However, LEN is properly defined in
*
Pass 2, in time to be used here.
JMP
START
Last instruction in program.
LAST
EQU
*
Address just past program end.
*
END
*
Two
levels of forward
referencing with EQU — unacceptable.
START
. ..
LENGTH
FCW
LEN
LEN is undefined at this point in both
*
Pass 1 and Pass 2 — error.
LEN
EQU
LAST-START
LAST is undefined at this point in Pass 1, and therefore so is LEN.
*
*
LEN is defined here in Pass 2,
*
but too late to use above.
JMP
START
Last instruction in program.
LAST
EQU
*
Address just past program end.
END
Рис. 6.4. Организация ссылок вперед с использованием псевдооперации EQU.
ровать сообщение об ошибке, если какие-либо идентификаторы еще не определены в конце первого прохода ассемблера.
Программа простого двухпроходного ассемблера для микро-ЭВМ Н6809 представлена на рис. 6.5. Во время первого прохода просматривается каждая строка и в таблицу символов вводятся встречающиеся метки. Каждая строка классифицируется согласно типу псевдооперации или машинной команды, указываемыми в поле «Код операции». Машинные команды классифицируются согласно их длине, выражаемой в байтах. Условные переходы представляют особый случай, когда ассемблер должен вычислить смещение от указанного адреса в поле операнда. После просмотра каждой строки содержимое счетчика ячеек программы обновляется на соответствующее значение
PROGRAM Assembler (input,output);
TYPE
charCol = 1..128; (Column numbers in input line}
byteVal = 0..255; wordVal = 0..65535; (Bytes and words}
strings = ARRAY [1..8] OF char; (Up to 8-char labels}
mnemonicType = (null,comment,org,equ,fcb,few,rmb,rmw,aend,
length1Instr,length2Instr,length3Instr,branchinstr)
VAR*
mnemonic : mnemonicType; opcode : byteVal; PLC : wordVal;
label8 : string8; inputLine : ARRAY [charCol] OF char;
opExpr : charCol; (Points to start of operand expr. in inputLine} gotOperand, gotLabel : boolean;
symbols : ARRAY [1..200] OF string8;’ (Symbol Table: 8-char symbols}
symVals : ARRAY [1..200] OF wordVal; (Symbol Table: 16-bit values}
PROCEDURE Enter (symbol : string8; symVal : wordVal);
( Enter a symbol and its value into the symbol table.}
FUNCTION Symbolvalue (symbol : string8) : wordVal;
( Find a symbol in the symbol table and return its value.}
PROCEDURE GetLine;
( Get next line of text from input file and put in inputLine.}
PROCEDURE ScanLine;
( Scan inputLine and perform the following functions:
' 1. Set gotOperand := false; gotLabel := false.
If the line begins with a *, set mnemonic := comment and exit.
If there is a label, set gotLabel:=true; label8: = (the label).
If a valid mnemonic is present, set mnemonic := (the corresponding mnemonicType constant); if it is a machine instruction, set opcode := (the proper opcode byte value).
If an operand is present, set gotOperand := true and set opExpr := (the starting column number of the operand).}
PROCEDURE GetNextOperand;
( Look at global variable opExpr, and update it to point to the start of the next operand expression in inputLine. If there are no more operands then set gotOperand:=false.}
FUNCTION Operandvalue : wordVal;
( Evaluate an operand expression in inputLine and return its value. The expression is assumed to begin in column opExpr in inputLine.
i The values of symbols that are encountered are looked up in the symbol table by calling Symbolvalue.}
PROCEDURE RewindSourceFile;
( Rewind the source file so that the next call of GetLine will fetch the first line of text again.}
PROCEDURE PutObject (addr : wordVal; objectByte : byteVal);
I Enter the value of objectByte into the object file so it will be placed at memory address addr when the object module is loaded.}
BEGIN
{PASS!}
PLC := 0; mnemonic := null;
WHILE mnemonic <> aend DO
BEGIN
GetLine; ScanLine;
IF gotLabel AND (mnemonicOequ) THEN Enter (labels, PLC) ;
CASE mnemonic OF null, comment, aend: ; {Do nothing} org: PLC := Operandvalue;
equ: Enter(label8,Operandvalue) ;
fcb: WHILE gotOperand DO BEGIN PLCr=PLC+1; GetNextOperand END; few: WHILE gotOperand DO BEGIN PLC:=PLC+2; GetNextOperand END; rmb: PLC := PLC + Operandvalue;
rmw: PLC := PLC + 2*OperandValue;
length 11nstr: PLC := PLC + 1;
length2Instr, branchinstr: PLC r = PLC + 2;
length3Instr: PLC := PLC + 3;
. END;
END;
(PASS2}
RewindSourceFile; PLC : = 0; mnemonic := null;
WHILE mnemonic <> aend DO
BEGIN
GetLine; ScanLine;
CASE mnemonic OF
, null,comment,equ,aend: ; (Do nothing} org: PLC := Operandvalue;
fcb: WHILE gotOperand DO BEGIN
PutObject (PLC, Operandvalue MOD 256)'; {8-bit value}
PLC := PLC + 1; GetNextOperand; END; few: WHILE gotOperand DO BEGIN
PutObject(PLC, Operandvalue DIV 256); (High-order byte}
PutObject(PLC+1, Operandvalue MOD 256),; {Low-order byte} PLC := PLC + 2; GetNextOperand; END;
rmb: PLC := PLC + Operandvalue;
rmw: PLC := PLC + 2*OperandValue;
lengthllnstr: BEGIN PutObject(PLC,opcode); PLC t= PLC+T END; length2lnstr: BEGIN PutObject(PLC,opcode) ;
PutObject(PLC+1,Operandvalue MOD 256); ^LC := PLC+2 END; length3Instr: BEGIN PutObject(PLC,opcode);
PutObject(PLC+1,Operandvalue DIV 256); {High-order byte}
PutObject(PLC+2,Operandvalue MOD 256); {Low-order byte} PLC := PLC + 3; END;
branchinstr: BEGIN PutObject(PLC,opcode) ; {Compute offset} PutObject(PLC+1, (Operandvalue-(PLC+2)+256) MOD 256);
PLC := PLC + 2; END;
END;
END;
END.,
Рис. 6.5. Программа двухпроходного ассемблера.
согласно типу команды, а в случае псевдоопераций — согласно количеству или значению операндов. При выполнении второго прохода содержимое счетчика ячеек программы корректируется аналогичным образом и формируется объектный модуль.
Подробное изучение большинства процедур, представленных на рис. 6.5, выходит за пределы нашего обсуждения. Например, вопросы, связанные с эффективными методами структурирования и управления таблицами символов, могли бы явиться предметом рассмотрения отдельной главы. На рис. 6.5 не показано, как ассемблер осуществляет поиск ошибок. Приведем наиболее распространенные ошибки в программах, которые выявляют с помощью ассемблера:
многократное определение метки. При выполнении первого прохода метка, уже находящаяся в таблице символов, была определена снова; '
наличие несуществующего кода операции. При выполнении первого или второго проходов в поле кода операции обнаружено нераспознаваемое мнемоническое обозначение;
неопределенная метка. При выполнении второго прохода поле операнда содержало метку, которая отсутствовала в таблице символов;
неправильная адресация. При выполнении второго прохода указывается команда с недоступным адресом (например, условный переход с относительным адресом, большим чем 127 байт);
синтаксическая ошибка. При выполнении первого или второго прохода обнаружен неразрешенный символ или неверно записанное выражение (например, X+:Y).
Конечно, ассемблер обнаруживает ошибки только, в формате языка ассемблера; сообщение, выдаваемое ассемблером после трансляции исходной программы НИ ОДНОЙ ОШИБКИ НЕ ОБНАРУЖЕНО, не является гарантией того, что после загрузки программа будет работать нормально.
ОБЪЕКТНЫЕ МОДУЛИ И ЗАГРУЗЧИКИ
Объектный модуль (программа или файл) содержит цепочки из битов, помещаемые в память микро-ЭВМ программой-загрузчиком. Символьная информация, которая содержалась в исходной программе, представленной на языке ассемблера, больше не требуется, и можно использовать простой и удобный формат. -
Типичный формат объектного модуля приведен на рис. 6.6а, где объектный модуль содержит набор «записей». Каждая запись имеет формат, представленный на рис. 6.6,6, и содержит байты команд и данных, загружаемые в последовательные ячейки памяти. Поле адреса указывает загрузчику адрес, по кото-
рому размещается первый байт данных, а поле счетчика байтов определяет количество последующих байтов. Если запись имеет нулевое значение счетчика байтов (рис. 6.6, в), то поле адреса этой записи интерпретируется как начальный адрес программы — адрес, по которому загрузчик может совершить переход после обработки последней записи.
Поле контрольной суммы в конце каждой записи представляет собой сумму всех байтов записи в дополнительном коде
адреса счетчика байтовадреса счетчика байтовконтрольной сум мь«контрольной сум мь«Рис. 6.6. Формат объектного модуля:а — полная программа; б —> типичная зайись; в — запись начального адреса программы.Рис. 6.6. Формат объектного модуля:а — полная программа; б —> типичная зайись; в — запись начального адреса программы.адресаконтроль -ной суммыадресаконтроль -ной суммы
адреса счетчика байтов
адреса счетчика байтов
контрольной сум мь«
контрольной сум мь«
Рис. 6.6. Формат объектного модуля:
а — полная программа; б —> типичная зайись; в — запись начального адреса программы.
Рис. 6.6. Формат объектного модуля:
а — полная программа; б —> типичная зайись; в — запись начального адреса программы.
адресаконтроль -
ной суммы
адресаконтроль -
ной суммы
(включая 2 байт поля адреса и 1 байт поля счетчика байтов)1). Это поле может проверяться загрузчиком в целях контроля целостности записи. Если имеют место какие-либо ошибки (пропадание знаков на ленте, помехи и т. д.) при операции записи или чтения, то с вероятностью 0,99 контрольная сумма записи не совпадет с контрольной суммой, вычисленной для искаженных данных. В табл. 6.2 представлен объектный модуль для исходной программы, приведенной на рис. 6.2 и 6.3. Байты, загружаемые с адреса 2000Н по 2019Н, могут быть организованы в одну длинную запись. Однако для облегчения восприятия печатного формата была установлена максимальная длина записи в 16 байт.
Обратите внимание на то, что новая запись начинается для байта по адресу 201СН, при этом по адресам 201 АН и 201ВН загрузка не производится. Это происходит потому, что соответствующие псевдбоперации RMB в исходной программе только резервируют необходимую область памяти; эти псевдооперации не вызывают записи в память каких-либо данных.
Рассмотрим теперь вопрос, связанный с загрузкой объектных модулей в виде файлов в дисковый накопитель или запоминающее устройство на магнитных лентах. Обычным способом
11 Сумма байтов в обратном коде позволяет более точно обнаружить определенные типы ошибок. По этому вопросу см., например, статью: Уокерли Дж. Обнаружение однонаправленных многократных ошибок с использованием арифметических кодов, требующих незначительных затрат. — IEEE Trans. Сотри!., С-24, 2, 210—212 (Мау 1975).
Таблица 6.2. Объектная программа для исходной программы, представленной на рис. 6.2 и 6.3
Адрес
Счетчик байтов
Байты данных
Контрольная сумма
2000
10
4F В7 20 IB Вб 20 IC В7 20 1А ВВ 20 1В
В7 20 1В
3G
2010
0А
Вб 20 1А 8В FF 26 F0 7Е 10 00
58
201С
01
0F
4G
2000
00
20
Примечание. Каждая строка в таблице представляет одну запись; все значения в таблице даны в шестнадцатеричной системе счисления.
является запись в память последовательности байтов, состоящих из 8 бит. Например, для записи объектной программы, приведенной в табл. 6.2, в дисковый накопитель или запоминающее устройство на магнитных лентах потребуется 43 байт: 8 байт для адресов, 4 байт для счетчиков, 4 байт для контрольных сумм и 27 байт для хранения действительных данных.
Используя указанный способ записи данных в память, невозможно организовать прямую выдачу объектного модуля на печать, поскольку байты не имеют никакого отношения к печатным символам Стандартного американского кода для обмена информацией (ASCII). В микровычислительных системах прибегают обычно к следующему решению: объектные модули хранятся в памяти как текстовые файлы, содержащие действительные печатные символы, которые приведены в табл. 6.2. Этот формат позволяет организовать прямую выдачу объектных модулей на печать, но при этом требуется вдвое увеличить занимаемую область памяти, поскольку 1 байт, содержащий информацию в шестнадцатеричном коде, представляется двумя символами. Использование подобного формата приводит к некоторому усложнению программы загрузчика, поскольку каждую считываемую пару символов необходимо представить в виде байта.
Несколько независимо транслированных объектных модулей можно объединить, чтобы создать одну большую программу. Существует возможность для одной или нескольких записей в объектном модуле хранить байты по одному и тому же адресу. В этом случае запись, ближайшая к концу модуля, обрабатывается в последнюю очередь и, следовательно, имеет наивысший приоритет. Последнее значение, загруженное по ■ конкретному адресу, заменяет предыдущее значение. Аналогично если имеются две или более записей с начальными исполнительными адресами, то последняя запись имеет более высокий приоритет перед другими. Предположим, например, что две записи в табл. 6.3
Таблица 6.3. Дополнительные записи, вносимые в объектную программу
Адрес
Счетчик байтов
Байты данных
Контрольная сумма
201С
01
0D
4А
2004,
00
24
присоединяются к объектной программе, представленной в табл. 6.2. Если бы это было выполнено, то программа начинала бы работу с ячейки памяти 2004 без установки в исходное состояние параметра SUM, а параметр CNT имел бы начальное значение 13ю вместо 15ю. Такие вынужденные изменения, вносимые в программу, разработчики называют «заплатами».
ПЕРЕМЕЩАЮЩИЕ АССЕМБЛЕРЫ И ЗАГРУЗЧИКИ
Перемещение
Адрес загрузки программы является первым адресом, по которому программа размещается в памяти. До сих пор мы предполагали, что адрес загрузки определяется псевдооперацией ORG и, следовательно, известен в процессе трансляции. Это позволяет ассемблеру использовать абсолютные адреса памяти при формировании адресных констант и машинных команд, которые имеют адреса в качестве операндов.
В большом программном проекте отдельные модули программы могут разрабатываться различными людьми в разное время, причем размер каждого модуля остается неизвестным до тех пор, пока не завершится его разработка. Кроме того, при отладке и обслуживании программы размеры модулей могут уменьшаться и увеличиваться. Как в этом случае производить назначение адресов загрузки?
Одним из подходов к решению этого вопроса является грубая оценка длины каждого модуля и выделение необходимой области памяти для его размещения, причем каждый модуль содержит соответствующий оператор ORG. Этот подход имеет два недостатка. Первый недостаток состоит в том, что в конце каждого модуля остается свободной часть памяти. Второй недостаток заключается в том, что можно неверно определить требуемый объем памяти для модуля. Эта ошибка не всегда очевидна для программиста. В таком случае загрузчик поместит часть одного модуля в начало другого модуля и при этом даже
не выдается никакого сообщения об ошибке. Но если ошибка обнаруживается, то необходимо выделить большую область памяти для длинного модуля и соответственно скорректировать адреса загрузки для других модулей.
Перемещающие ассемблеры и перемещающие загрузчики устраняют эти проблемы посредством определения адресов загрузки во время процесса загрузки модулей. Для выполнения этой процедуры перемещающие ассемблеры и загрузчики различают абсолютные и перемещаемые величины: значение абсолютной величины (идентификатора или выражения) не зависит от адреса, по которому производится загрузка программы; значение перемещаемой величины (идентификатора или выражения) находится в зависимости от адреса, по которому производится загрузка программы.
Перемещающий ассемблер располагает целым набором правил для того, чтобы различать абсолютные и перемещаемые величины:
числовая константа является абсолютной величиной;
значение счетчика ячеек программы является перемещаемой величиной;
идентификатор, находящийся в поле метки оператора, не являющегося оператором EQU, является перемещаемой величиной;
идентификатор, находящийся в поле метки оператора EQU, имеет такой же тип, как и величина в поле операнда;
выражение, содержащее только абсолютные константы и идентификаторы, имеет абсолютную величину;
если параметр REL является перемещаемой величиной, а параметр ABS абсолютной константой, идентификатором или выражением, то следующие формы выражений принимают перемещаемые значения: REL, REL-j-ABS, ABS + REL, REL—ABS;
разность двух перемещаемых величин (REL—REL) является абсолютной величиной. (Смещение между двумя адресами остается постоянным при перемещении программы);
следующие формы выражений недопустимы: REL+REL, ABS—REL, REL*REL, REL*ABS, ABS*REL. В данном случае символ звездочка представляет любую операцию, кроме сложения или вычитания. •
На рис. 6.7 приведена перемещаемая программа на языке ассемблера для микро-ЭВМ М6809 (некоторые машинные команды этой программы будут описаны в гл. 7 и 8). Псевдооперация RORG addr указывает, что адрес загрузки является перемещаемым адресом addr. Затем ассемблер формирует соответствующие команды и данные, как если бы программа загружалась в память по адресу addr. Однако ассемблер также до-
LN# PLC
CONTENTS LABEL
OPCODE OPERAND
COMMENTS
001 0000
RORG 0
Start at relocatable addr 0.
002 0000
KBITS
EQU8
8-bit division.
003 0000
DVND
RMB2
2-byte dividend placed here.
004 0002
DVSR
RMB1
Divisor placed here.
005 0003
QUOT
RMB1
Reserve byte for quotient.
006 0004
REM
RMB1
Reserve byte for remainder»
007 0005
CNT
RMB1
Counter storage.
008 0006
002BPRGLEN
FCWLENGTH
Length of program.
009 0008
86 08DIVIDE
LDA#NBITS
Initialize count.
010 000А
B7 0005R
STACNT
011 000D
B6 0000R
LDADVND
Put dividend in A,Bt
012 0010
F6 0001R
LDBDVND+1
013 0013
31 0002R
CMPA DVSR
Will quotient fit in 1 byte?
014 0016
25 03
BLODIVLUP
Branch if it will.
015 0018
7E 1800
JMPSYSOVF
Else report overflow.
016 001В
58DIVLUP
ASLB
Left shift A,В with LSB:=0.
017 001С
49
ROLA
A carry here from MSB means
018 001D
25 05
BCSQU0T1
high DVND surely > DVSR.
019 001F
B1 0002R
CMPA DVSR
Compare high DVND with DVSR.
020 0022
25 04
BLOQUOTOK
Quotient bit = 0 if lower.
021 0024
5CQU0T1
INCB
Else set quotient bit to 1.
022 0025
BO 0002R
SUBA DVSR
And update high DVND.
023 0028
7A 0005R QUOTOK
DECCNT
Decrement iteration count.
024 002B
2E EE
BGTDIVLUP
Continue until done.
025 002D
B7 0004R
STAREM
Store remainder.
026 0030
F7 0003R
STBQUOT
Store quotient.
027 0033
39
RTS
Return.
028 0034
SYSOVF
EQU1800H
System overflow report addr*
029 0034
ENDADR
EQU*
030 0034
LENGTH
EQU ENDADR-DIVIDE
031 0034
END DIVIDE
SYMBOL TABLE:
SYMBOL VALUESYMBOL
VALUESYMBOL VALUESYMBOL VALUE
№ITS- 1
0008DVND
OOQORDVSR
0002RQUOT0003R
REMl
0004RCNT
0005RPRGLEN 0006RDIVIDE 0008R
DIVLUP 1
D01BRQU0T1
0024RQUOTOK 0028RSYSOVF 1800
ENDAD4 1
D033RLENGTH
002B
Рис. 6.7. Перемещаемая подпрограмма деления чисел без знака для микро- ЭВМ. 6809, использующая алгоритм, представленный на рис. 4.9.
бавляет к объектному модулю перечень всех ячеек, содержащих перемещаемые величины, значения которых должны корректироваться, если программа загружается по другому адресу. Во время загрузки программист указывает константу, называемую смещением загрузки, которую загрузчик прибавляет к адресу загрузки перемещаемого объектного модуля и ко всем перемещаемым величинам, как только они загружаются в память.
Все 16-разрядные величины, помеченные символом R на рис. 6.7, являются перемещаемыми. Загрузочный модуль для этой программы имеет список, состоящий из девяти адресов (000В, 000Е, ООП, 0014, 0020, 0026, 0029, 002Е, 0031), которые содержат 2-байтовые перемещаемые величины. Если в процессе загрузки указывается смещение загрузки, равное 2000, то адрес загрузки равен 2000, начальный адрес равен 2008, а загрузчик прибавляет число 2000 ко всем перемещаемым величинам (например, ячейки 200В, 200С хранят величину 2005).
Три идентификатора, приведенных на рис. 6.7, дают представление об использовании абсолютных величин: идентификатор, приравненный к числовой константе, предназначенной для счета или аналогичных целей, является абсолютной величиной (NBITS); разность между двумя адресами является абсолютной величиной (LENGTH); фиксированный адрес за пределами программы не зависит от адреса загрузки и, следовательно, является абсолютной величиной (SYSQVF).
Редактирование связей
Программа, представленная на рис. 6.7, связывается с другой программой с помощью абсолютного адреса (1800Н). Однако при разработке больших программ этот способ обычно не используется. Так как программист не располагает информацией о конечном адресе загрузки модуля во время трансляции, он
EXT PTIME PTIME subroutine is defined elsewhere* MAIN . . .
JSR PTIME • Print current time of day.
Рис. 6.8. Программа с внешней ссылкой.
также не знает и адресов глобальных переменных и подпрограмм. Следовательно, перемещающий ассемблер предоставляет еще одно средство — возможность определять символические точки входа и внешние ссылки, которые разрешаются в процессе загрузки.
Псевдооперация EXT (Внешние ссылки) позволяет получить список идентификаторов, которые определяются вне текущего программного модуля, представленного на языке ассемблера. С помощью псевдооперации ENT (Точки входа) формируют список идентификаторов, которые определяются в текущем модуле, но значения которых используются в других программных модулях. Набор модулей, использующих псевдооперации ENT и EXT, представлен на рис. 6.8—6.10. Обратите внимание, что
псевдооперации ENT и EXT могут обращаться либо к адресам глобальных данных (HOUR, MINUTE), либо к адресам команд (PTIME).
Подобно абсолютным и перемещаемым выражениям, внешние выражения должны соответствовать специальному формату. Следующие формы выражений приобретают внешние значе-
*
This routine prints the current time of day on a terminal. EXTHOUR,MINUTEHOUR and MINUTE are. defined elsewhere.
PTIME
ENTPTIMEPTIME may be called by other programs.
LDAHOUR
LDAMINUTE
-RTSReturn to calling program.
Рис. 6.9. Программа с внешними ссылками и точками входа.
ния, если псевдооперация EXT является внешним идентификатором: EXT, EXT+ABS, ABS + EXT, EXT—ABS. Все другие формы, включающие псевдооперацию EXT, принимают неопределенные значения.
Поскольку внешние символические ссылки не реализуются до процесса загрузки, перемещающий ассемблер должен включить следующую дополнительную информацию в объектный мо-
* The operating system runs this subroutine once per minute
*
TIMER
to update the time of day.
ENT LDA INCA
HOUR,MINUTE MINUTE
Global variables.
LDA
HOUR
RTS
Return.
HOUR
RMB
1
MINUTE
RMB
1
Рис. 6.10. Программа с точками входа.
дуль: 1) таблицу всех внешних символов и ячеек объектного модуля, значения которых зависят от внешних символов; 2) таблицу всех символов входа и их значений. Перемещающий загрузчик затем обрабатывает все объектные модули, программы, формируя свою таблицу внешних символов и идентификаторов точек входа. Поскольку размеры объектных модулей известны, их можно оптимально разместить в памяти. В это время могут быть обнаружены такие ошибки, как не определенные внешние идентификаторы или многократно определенные внеш-
ние идентификаторы. Загрузчик может автоматически производить поиск библиотеки стандартных функций (математических, ввода-вывода, процедур общего пользования), когда обнаруживаются не определенные идентификаторы (символы). В любом случае все адреса ячеек памяти, которые зависят от внешних идентификаторов, корректируются путем прибавления значения внешнего идентификатора.
Загрузчик, который реализует внешние ссылки, перемещает объектные модули и загружает их в память, называется редактирующим загрузчиком. В некоторых системах отдельная программа под названием редактора связей реализует внешние ссылки и объединяет объектные модули в один непрерывный перемещаемый объектный модуль без загрузки его в память. Новый сформированный объектный модуль затем можно загружать в любую область памяти. Эту процедуру выполняет простой перемещающий загрузчик, который не выполняет никаких функций по редактированию связей между модулями.
МАКРОКОМАНДЫ
Иногда возникает необходимость использовать определенную последовательность команд во многих частях программы. Предположим, мы хотим передать 8-битовую величину из аккумулятора А микро-ЭВМ Н6809 в младший байт индексного регистра X и установить старший байт регистра X в нуль. Это можно выполнить с помощью нижеследующей последовательности команд, предполагая, что 2-байтовая временная переменная ТЕМРХ уже сформирована в программе оператором ТЕМРХ RMW 1.
STA
ТЕМРХ+1
Поместить содержимое аккумулятора А в младший байт переменной ТЕМРХ
CLRA
Произвести сброс старшего байта переменной ТЕМРХ
STA
ТЕМРХ
LDX
ТЕМРХ
Загрузить 16-битовую величину ТЕМРХ в индексный регистр X
Хотя память должна выделяться переменной ТЕМРХ только один раз, указанную выше последовательность команд необходимо повторять всякий раз, когда содержимое аккумулятора А передается в индексный регистр X. Макроассемблер позволяет избежать такой рутинной процедуры, давая возможность программисту указывать только макрокоманду (макрос), которая эквивалентна данной последовательности команд. Макроассемблер выполняет все функции стандартного ассемблера, а также некоторые дополнительные функции. Свойства и точные форма-
ты макрокоманд весьма разнообразны, но все макроассемблеры выполняют основные функции, которые указаны ниже.
Две новые псевдооперации MACRO и ENDM дают возможность программисту определять новые мнемонические обозначения, которые замещают последовательность команд. В приведенном выше примере программист может поместить следующее макроопределение в начале программы:
TFRAX
MACRO
Без
STA ТЕМРХ4-1Записать содержимое аккумулятора А в
младший байт переменной ТЕМРХ
CLRAПроизвести сброс старшего байта перемен
ной ТЕМРХ
STA ТЕМРХ
LDX ТЕМРХЗагрузить 16-р азрядную величину ТЕМРХ
в индексный регистр X
ENDM
действительного генерирования каких-либо машинных
команд
в этом примере определяется мнемоническое обозначе-
ние TFRAX, соответствующее последовательности четырех
команд, обеспечивающих передачу содержимого аккумулятора А в индексный регистр X. -
Ниже в программе для передачи 8-битовой переменной величины J в индексный регистр X можно использовать следующие операторы:
LDA J TFRAX
Макроассемблер текстуально заменяет строку TFRAX заранее определенной последовательностью четырех строк. Строка TFRAX называется макровызовом, а текстуальная замена является макрорасширением. Полезность макрокоманд возрастает при использовании параметров. Макроопределение может содержать формальные параметры; в приводимом ниже примере VAR является формальным параметром:
TFRVX MACRO
VAR
LDA
VAR
Поместить переменную величину в аккумулятор А
STA
TEMPX+1
Поместить содержимое аккумулятора А в старший байт переменной ТЕМРХ
CLRA .
Произвести сброс старшего байта переменной ТЕМРХ
STA
ТЕМРХ
LDX
ТЕМРХ
Загрузить 16-разрядную величину ТЕМРХ в регистр X
ENDM
При вызове макрокоманды TFRVX указываются фактические параметры, которые заменяют формальные параметры, когда выполняется расширение макрокоманды. Следовательно, строка
TFRVX J
формирует команды передачи величины J в индексный регистр X. В действительности строка генерирует точно такие же машинные команды, как и предыдущая последовательность, состоящая из двух строк, соответствующая макрокоманде TFRAX.
Обобщенная форма любого макроопределения с параметрами имеет вид .
NAME MACRO FP1, FP2,.... FPN
...Макротело
ENDM
Такая макрокоманда вызывается оператором
NAME АР1.АР2APN
Когда выполняется расширение макрокоманды, то каждый формальный параметр FPi в макротеле текстуально заменяется соответствующим фактическим параметром APi.
Макровызовы могут быть вложенными, т. е. тело одного макроопределения может содержать вызов другой макрокоманды. Например, мы можем определить макрокоманду TFRVX следующим образом:
TFRVX MACRO VAR.
LDA VARЗагрузить VAR в А,В
TFRAXПередать А в X
ENDM
Макроассемблер располагает некоторым количеством дополнительных псевдокоманд. Одной из таких команд является команда SET, аналогичная псевдокоманде EQU, с тем лишь исключением, что она позволяет переопределять идентификатор без формирования сообщения об ошибке. Чтобы продемонстрировать, как это происходит, рассмотрим следующую программу. Программист хочет зарезервировать один непрерывный блок памяти для всех переменных, однако определение переменных производится на протяжении всей программы вплоть до подпрограмм, которые ими пользуются. Чтобы это выполнить, програм-
мист резервирует область памяти и определяет макрокоманду в начале программы:
VARBLK
RMB
100
Выделить 100 байт для переменных величин
VAREND
EQU
*
NEXTV
SET
VARBLK
RESERV
MACRO VAR,SIZE Зарезервировать область памяти SIZE для переменной VAR
VAR
NEXTV
EQU NEXTVВвести VAR в таблицу символов
SET ' NEXTV+SIZE Поместить NEXTV по очередному адресу ENDM
Далее в программе три 1-байтовые переменные и одна 2-байтовая переменная могут быть описаны следующим образом:
RESERV HOUR.1
RESERV MINUTE,1
RESERV SECOND,1
RESERVDATE,2
Макроассемблеры могут также содержать псевдокоманды условной трансляции, которые обеспечивают трансляцию соответствующих операторов, если выполняется определенное условие. Условие проверяется во время трансляции программы, а не во время ее прогона (выполнения). Например, следующая версия RESERV формирует «сообщение об ошибке», если имеется превышение выделенного объема памяти для данных.
RESERV MACRO VAR,SIZE IFGT NEXTV+SIZE—VAREND
•• ПРЕДУПРЕЖДЕНИЕ!! ПРЕВЫШЕНА ВЫДЕЛЕННАЯ ОБЛАСТЬ ПАМЯТИ ДЛЯ ПЕРЕМЕННЫХ ВЕЛИЧИН!**
ENDIF
IFLE • NEXTV+SIZE—VAREND
VAR EQUNEXTV
NEXTV SETNEXTV+SIZE
ENDIF
ENDM
В приведенном фрагменте программы оператор IFGT expr вызывает обработку последующих операторов, включая ENDIF, если выражение ехрг>0, в противном случае пропускает их. Аналогично оператор IFLE ехрг обрабатывает операторы, если выр ажение ■ ехрг < = 0.
Иногда необходимо использовать метку в пределах макротела, например:
TST32Z
MACRO VAR
Проверить 32-битовую переменную величи
LDX VAR
ну на наличие нуля Проверить старшее слово
BNE .LI
Слово не равно нулю, если старшее слово
LDX VAR+2
не равно нулю
Проверить младшее слово
.L1
EQU *
Адрес после конца расширения TST32Z
♦
ENDM
Z равен 1, если 32-битовое слово равнялось нулю
Одна и та же метка (.L1) используется всякий раз, когда производится вызов макрокоманды, что в обычных условиях привело бы к ошибке многократного определения идентификатора при вторичном вызове макрокоманды. Однако мы разрешили проблему благодаря использованию другого способа — любая метка с предшествующей точкой (.) предполагается локальной относительно текущего макрорасширения. Когда ассемблер вводит метку .L1 в таблицу символов, то получает дополнительную информацию, чтобы различать данную метку от других применений той же метки0.
В различных макроассемблерах проблема определения меток решена по-разному. Некоторые макроассемблеры позволяют программисту определять идентификаторы в пределах макрокоманды или как локальные, или как глобальные. Некоторые из них имеют средства для автоматического генерирования идентификаторов (например, L00001, L00002 и т. д.). При использовании большинства простых макроассемблеров программист должен назначать единственную метку в качестве параметра при каждом вызове макрокоманды.
Макроассемблер является весьма эффективным программным средством. Действительно, в принципе возможно написать набор макрокоманд для макроассемблера для одной ЭВМ таким образом, что он будет распознавать мнемонические обозначения совершенно другой ЭВМ и формировать для нее объектный код. Наиболее мощные макроассемблеры допускают такие операции, как определение новых макрокоманд в составе макровызова и объединение строк для формирования идентификаторов. •
*> Конечно, опытный программист избежал бы этой сложной ситуации, изменив команду условного перехода на команду BNE*+3, зная, что условный переход обеспечит пропуск 3-байтовой команды. Действительно ли это так? В реальной команде LDX VAR+2 микро-ЭВМ 6809 содержится только 2 байт, если адрес VAR+2 находится на странице прямой адресации. Следовательно, опытный программист внес в программу коварную ошибку.
СТРУКТУРНЫЙ ЯЗЫК АССЕМБЛЕРА
Языки высокого уровня блочной структуры, такие, как Паскаль, являются мощным и эффективным средством программирования благодаря ряду свойств, которыми не обладают «неструктурные» языки ассемблера:
компилятор транслирует каждый оператор языка высокого уровня в последовательность машинных команд, предназначенных для выполнения требуемого действия, включая вычисление арифметических и логических выражений во время выполнения программы;
язык позволяет разделять программы и операторы на естественные иерархические блочные структуры;
благодаря блочной структуре компилятор применяет набор правил, относящихся к областям определения имен переменных и других идентификаторов, чтобы упростить соглашения по формированию имен;
имеется возможность применять операторы высокого уровня, такие, как WHILE, REPEAT и FOR, чтобы упростить организацию циклов;
язык предоставляет средства для структурных типов данных, определяемых пользователем;
предусмотрена возможность для программиста писать исходную программу в свободном формате, используя разделители и комментарии для улучшения восприятия текста.
Все перечисленные качества свойственны и структурному языку ассемблера, за исключением первого, — каждый оператор языка ассемблера транслируется только в одну машинную команду.
Программа DIVIDE, изображенная на рис. 6.7, представлена в виде гипотетической структурной программы на языке ассемблера для микро-ЭВМ 6809 (рис. 6.11), который мы назовем языком SCAL (структурно-кодированный язык ассемблера). Программа на языке SCAL выглядит подобно программе на языке Паскаль, но формирует почти такие же машинные команды, как и неструктурная версия программы на языке ассемблера.
В программах на языке SCAL пробелы, новые строки, двоеточия, точки с запятой и круглые скобки используются подобно тому, как они применяются в языке Паскаль для форматирования и комментирования программ. Значение зарезервированных слов аналогично значению слов языка Паскаль:
PROCEDURE: указывает начало блока процедуры, начиная новую область определения имен переменных и других идентификаторов. Параметры не указываются, но могут при необходимости обрабатываться версией языка ассемблера SCAL, ис-
пользующего соглашение по стековой организации памяти.
VAR: резервирует область памяти для переменных указанных типов; ассемблер SCAL распознает стандартную длину переменных, такую, как байт и слово. Переменные имеют область действия, ограниченную блоком, в котором они объявлены. Зарезервированное слово VAR заменяет псевдооперации RMB ц RMW неструктурного языка ассемблера.
LABEL divaddr, endaddr;
VAR dvnd : word; dvsr, quot, rem : byte;
proglength : DATAWORD endaddr-divaddr;
PROCEDURE Divide;
CONST nbits = 8; sysovfl = 1800H;
VAR ent : byte;
Begin
divaddr: {Define starting address of procedure code.l
Ida #nbits; sta ent; {ent := # of bits/byte}
Ida dvnd; Idb dvnd+1; {a,b := dvnd} empa dvsr; {Will quotient fit in one byte?} IF HS THEN jmp sysovf; {Report overflow if not.}; REPEAT
aslb; rola; {Double-prec. arithmetic shift a,b}
IF CS THEN * {High dvnd > dvsr, set quotient bit} BEGIN ineb; suba dvsr END {and update dividend.} ELSE {Trial subtraction needed.}
‘BEGIN
empa dvsr;
IF HS THEN {High dvnd >= dvsr, set quotient bit^ BEGIN ineb; suba dvsr END {and update dividend.}.
END;
dec ent;
UNTIL LE; {Repeat for all bits.} sta rem; stb quot; {Save results.} rts; {Return.} endaddr: {Define last address of procedure code.}
END;
Рис. 6.11. Структурированная версия программы DIVIDE на языке ассемблера.
DATAWORD: в процессе загрузки обеспечивает запись в память последовательной цепочки слов с указанными значениями, заменяя псевдооперацию FCW неструктурного языка ассемблера.
DATABYTE: в процессе загрузки обеспечивает запись в память последовательной цепочки байтов с указанными значениями, заменяя псевдооперацию FCB неструктурного языка ассемблера.
CONST: в процессе трансляции отождествляет идентификатор с указанной константой или постоянным выражением, заменяя псевдооперацию EQU неструктурного языка ассемблера.
Таблица 6.4. Сравнение структурных операторов языка SCAL и неструктурных эквивалентов языка ассемблера
В (NOT условие) labelA
label А;
оператор "
В (NOT условие) labelA оператор!
BRA labelB
labelA;
оператор2
labelB;
labelA;
список операторов
В (NOT условие) labelA
BRA labelB
labelA;
оператор
labelB;
В(условие) labelA
Эквиваленты языка ассемблераЭквиваленты языка ассемблераСтруктурные операторы языка SCAL
Эквиваленты языка ассемблера
Эквиваленты языка ассемблера
IF условие
THEN оператор
IF условие
THEN оператор!
ELSE оператор2
REPEAT список операторов UNTIL условие
WHILE условие DO оператор
Константы имеют область действия, ограниченную блоком, в котором они определены.
LABEL: объявляет идентификатор в качестве разрешенной метки оператора или данных в'текущей области действия. Если метка не объявлена зарезервированным словом LABEL, то ее .область действия ограничена процедурой, в которой она определена. Например, оператор DATAWORD (рис. 6.11) будет содержать не определенные идентификаторы, если еще не были объявлены endaddr и divaddr, поскольку они определяются в пределах процедуры блока более низкого уровня.
BEGIN, END: ограничивают составные операторы, включающие тела процедур, как это принято в алгоритмическом языке Паскаль.
Подобно языку Паскаль, язык ассемблера SCAL содержит как простые, так и структурные операторы. Стандартные операторы языка ассемблера, такие, как Ida^nbits, выражают простые операторы языка SCAL. Структурные операторы включают сложные операторы IF, REPEAT и WHILE. В табл. 6.4 представлены неструктурные эквиваленты условных и циклических операторов языка SCAL. В языке Паскаль операторы могут осуществлять проверку условий, которые являются произвольными булевыми (логическими) выражениями, а в языке SCAL требуется, чтобы условие было одним из тех, проверка которых обеспечивается командами условного перехода микроЭВМ 6809. Условие указывается последними двумя буквами в мнемоническом обозначении команды перехода (см. табл. 8.3). Проверка условия должна следовать за машинной командой,
^которая производит установку разрядов условий, подлежащих проверке. Следовательно, операторы языка SCAL(pnc. 6.11)
-cmpa dvsr; {Частное помещается в 1 байт?}
IF HS THEN jmp sysovf; {Выдать сообщение о переполнении, если частное не помещается в 1 байт}
-формируют машинные команды согласно командам языка ас- -семблера, которые были приведены на рис. 6.7:
CMPA DVSR
BLO DIVLUP NOT HS (больше или равно) =LO (меньше)
JMP SYSOVF
DIVLUP ...
В программах, содержащих сложные циклы, структурные операторы дают возможность выражать алгоритм управления -более четко, чем при использовании условных переходов. Структурные операторы могут привести к удлинению программы по сравнению с программой, содержащей «переплетающиеся» переходы. Например, две команды (incb, suba dvsr) включены в программу на рис. 6.11 дважды, тогда как при использовании неструктурной версии языка ассемблера их употребляют в программе только один раз. Однако следует заметить, что улучшение читаемости программы оказывается более ценным фактором, чем незначительное увеличение длины программы1).
В данном разделе представлены только краткие сведения о структурном языке ассемблера. Подобно неструктурным языкам ассемблера, структурные языки ассемблера широко различаются по составу команд и выполняемым функциям. Многие ЭВМ располагают только неструктурным языком ассемблера. Исключением является микро-ЭВМ Z8000 фирмы Zilog, для которой разработан структурный язык ассемблера PLZ/ASM.
УПРАЖНЕНИЯ
€.1. Исключите строку 010 из программы, приведенной на рис. 6.3, и вручную выполните трансляцию программы, показывая изменения, происходящие в коленках LN#, PLC, CONTENT и в таблице символов.
Найдите синтаксические ошибки в приведенной миже программе, представленной на языке ассемблера для микро-ЭВМ Н6809:
!) Если функция короткая, то ее можно повторить в программе, если же она длинная, то ее следует определить в качестве отдельной процедуры, а повторяющиеся сегменты заменить процедурными вызовами. В любом случае в конфликтных ситуациях можно пренебречь структурными операторами и использовать переходы в программе.
ORG
2000H
INIT
CLRA
Начальная установка переменных
STA
VI
STA
LI
Выполнение некоторых вычислений
LOOP
LDA
V3
Получение значения V3
STA
V2
Запись в память
ADD
VI
BEQ
LI
STA
V3
L1
STA
VI
BMI
LOOP
Продолжать до тех пор, пока значение поло
жительно
BEQ
SYSOVF
RET
По выполнении возврат
VI
RMB
1 -
V3
RMB
1
L1
FCB
0D
SYSOVF EQU
1990
END
INIT
Если выражение операнда имеет значение —12ю, какое значение будет возвращено функцией OperandValue в программе, приведенной на рис. 6.5? 6.4. Псевдооперация FCC (константа формирования символов) имеет один операнд, строку символов, заключенную в ограничители, например в одиночные кавычки:
FCC ZA 17-byte string/ (Строка, содержащая 17 байт)
Псевдооперация FCC предназначена для хранения значений символов, представленных в коде ASCII, в последовательных ячейках памяти. Модифицируйте программу, представленную на рис. 6.5, так, чтобы она обрабатывала псевдооперацию FCC.
Объясните, как вычисляется значение смещения в дополнительном коде с помощью команды Branchinstr (рис. 6.5) для выполнения команды перехода.
Предположите, что оба оператора RORG и ORG присутствуют в (частично) перемещаемой программе. Измените второе и третье правила для распознавания абсолютных и перемещаемых величин таким образом, чтобы обеспечить правильную обработку меток и счетчика ячеек программы.
Предположите, что объектный модуль, сформированный перемещаемым ассемблером микро-ЭВМ 6809, записывается в память в виде последовательности печатных символов в коде, как указано в разд. 6.3. Дайте полную спецификацию формата объектного модуля, включая список адресов, содержащих перемещаемые величины, и список внешних меток (символов) и меток входа. ’ ,
Возможно ли организовать вложение псевдоопераций IF? Каким образом ассемблер в таком случае выполняет согласование операторов END IF с соответствующими псевдооперациями IF?
Определите псевдооперацию ELSEIF, которая позволяет псевдооперации IF производить трансляцию одной или другой последовательности операторов. Как ассемблер осуществляет согласование псевдоопераций ELSEIF и
ENDIF с соответствующими псевдооперациями IF? Каким образом это влияет на вложение псевдоопераций IF?
Используя время выполнения команд, которое указано в упр. 5.6, напишите макрокоманду DELAY с одним параметром Т таким образом, чтобы расширенная программа обеспечила задержку точно на 1 мкс. (Поскольку требуется только несколько команд, вы можете определить их время выполнения, даже если вы не сделали упр. 5.6.) Предположите, что параметр Т представляется как 16-битовое число в дополнительном коде и не формирует задержки, если он равен нулю или имеет отрицательное значение. Расширенная программа должна иметь менее 20 команд для любого значения параметра Т. Определите все новые псевдооперации (например, IFEQ) в операторы (например, DIV), которые вы сочтете необходимым включить в макрокоманду.
Перепишите программу умножения, представленную на рис. 5.6, используя язык SCAL. Ваша версия программы не долж,на точно формировать те же самые машинные команды.
Перепишите программу счета в обратном коде, представленную на рис. 5.14, используя язык SCAL. Ваша версия программы не должна точно формировать те же самые машинные команды.
В структурном языке ассемблера, подобном языку SCAL, оператор WHILE может содержать ошибку, которая не распространяется на оператор REPEAT. Найдите ошибку в следующей версии подпрограммы счета в обратном коде, представленной на языке SCAL (рис. 5.14), и объясните причину ее возникновения.
PROCEDURE Bent1s;
, BEGIN
sta ebyte; Idx #masks; clra; sta blent; {initialization} WHILE NE DO {Do while X not pointing past last mask.}
BEGIN
Ida @x; anda ebyte; {Check for a 1.}
IF NE THEN {Update count.}
BEGIN Ida blent; adda #1; sta blent END;
addx #1; empx #maske; {Bump X to next mask*} END;
Ida blent; rts; END;
Допускается использование различных форматов для оператора WHILE, чтобы устранить проблему, имеющую место в приведенном выше примере. Объясните указанный ниже формат, приведя эквивалентные операторы .на языке ассемблера, которые он должен сформировать. Примените новый one* ратор WHILE к указанной выше процедуре Bcntls.
WHILE statement-list condition DO statement
(WHILE список операторов условие DO оператор)
Запишите на версии языка SCAL представленный на рис. 6.11 цикл REPEAT, который формирует не больше машинных команд, чем цикл, описанный на неструктурном языке ассемблера, и не использует команд условного перехода. Стоит ли сокращение числа машинных команд затрачиваемых на это времени и усилий?
Эквиваленты языка ассемблера структурных операторов языка SCAL (табл. 6.4) могут не выполнить своих функций, если они должны управлять очень длинными цепочками операторов. Объясните, почему. Напишите эквиваленты, которые обеспечат работу с управляющими операторами любой длины.
ЛИТЕРАТУРА
Хотя всеми признанный стандартный язык ассемблера отсутствует, Комитет стандартов Института инженеров по электротехнике и радиоэлектронике (IEEE) сделал попытку разработать подобный язык. Первый проект такого языка был опубликован в журнале Computer [Fischer W. Р., Microprocessor Assembly Language Draft Standard, 12, 12 (1979)].
Наиболее точное описание языка ассемблера для конкретной ЭВМ, как правило, можно найти только в программной документации фирмы-изготовителя этой ЭВМ. Достаточно полные сведения по использованию ассемблеров» загрузчиков и макропроцессоров содержатся в книгах Донована (Donovan J. J., Systems Programming McGraw-Hill, 1972) и Баррона (Barron D. W., Assemblers and Loaders, MacDonald/Elsevier, 1969). Петерсон привел примеры программ на языке ассемблера MIX и примеры загрузчика в сборнике Computer Organization and Assembly Language Programming, Academic Press, 1978.
Структурные языки ассемблера уже в течение определенного времени находят применение. Н. Вирт описал такой язык для ЭВМ Системы 360 фирмы IBM (Wirth N., PL/360,. A Programming Language for the 360 Computers, Jour* nal of the ACM (January 1968). Фирма Zilog представила сведения по языку ассемблера PLZ/ASM в журнале Computer [Bass Ch., PLZ: A Fdmily of System Programming Languages for Microprocessors (March 1978)].
77СПОСОБЫ АДРЕСАЦИИСПОСОБЫ АДРЕСАЦИИ
7
7
СПОСОБЫ АДРЕСАЦИИ
СПОСОБЫ АДРЕСАЦИИ
Для того чтобы выполнять полезную работу, вычислительная машина должна обрабатывать данные, хранимые в памяти. Команды обработки данных содержат адреса, которые указывают местонахождение требуемых данных. Размерность адреса определяет максимальную область памяти, доступную для процессора, например 16-разрядный адрес означает, что может использоваться область памяти 64К байт. Согласно утверждению разработчиков семейства ЭВМ PDP-111), «самая большая и наиболее распространенная ошибка, которая может быть совершена при конструировании ЭВМ, состоит в том, что не обеспечивается достаточное количество адресных разрядов для обращения к памяти и управления. Совершенно очевидно, что через каждые 2—3 года требуется увеличивать адрес на один разряд, так как цены на запоминающие устройства снижаются ежегодно на 30%, а пользователи хотят приобретать более совершенные модели вычислительных систем по постоянной цене».
Из всех ЭВМ, приведенных в этой книге, лишь некоторые обладают достаточным количеством адресных разрядов, чтобы полностью удовлетворить потребности предполагаемой сферы применения. Команды ЭВМ 68 000 и Z8000 содержат 31 и 32 адресных разряда, т. е. определяют область памяти более 2 млрд. байт. Другие ЭВМ для увеличения области физической памяти в системе могут использовать карту распределения памяти (этот вопрос обсуждается в последнем разделе данной главы).
Хотя наиболее простым способом указания адреса является включение его в состав команды, манипуляции адресом, в которых обычно возникает необходимость, предполагают дополнительные более удобные или эффективные способы адресации. Некоторые из этих способов адресации имеют следующее назначение:
разрешить команде осуществлять доступ к ячейке памяти, адрес которой вычисляется во время прогона программы,
> Bell С. G., Mudge J. С., The Evolution of the PDP-11, Computer Engineering: A. DEC View of Hardware Systems Design, by C. G. Bell, J. C. Mudge, J. E. McNamara, Bedford, Digital Press, 1978, pp. 381—382.
обеспечивая при этом эффективный доступ к массивам, спискам и другим структурам данных;
манипулировать адресами в форме, наиболее подходящей для таких широко используемых структур данных, как стеки и одномерные массивы;
указывать полный адрес памяти наименьшим количеством разрядов, следовательно, делать команду короче;
вычислять адреса относительно местоположения команды, чтобы обеспечить загрузку программы в любую область памяти без изменения программы.
Ни один процессор не располагает командами со всеми спо< собами адресации, однако мы рассмотрим все наиболее важные из них.
ОБЩИЕ ПОНЯТИЯ
Число разрядов в адресе и в типичном регистре или данных в процессоре не всегда одинаково, но между ними существует тесная связь. В табл. 7.1 приведены в обобщенном виде характеристики адресного пространства и регистров всех процессоров, описываемых в части III. Длина адреса обычно составляет 1 или 2 размера регистра. Следовательно, адреса можно хранить в регистрах или парах регистров, а одни и те же регистры и команды использовать для обработки данных и адресов»
Большая часть способов адресации, описанных в двух следующих разделах, свойственна процессору 6809 фирмы Motorola. По этой причине мы приведем ряд примеров, обратившись к командам процессора 6809. Для получения более подробных сведений относительно представления способов адресации в
PDP-11
16
16
68000
32D
32
Z8000
31D
16
9900
16
16
6809
16
8 и 16
8086
162>
16
MCS-48
8 и 12
8
Таблица 7.1. Адресные характеристики процессоров, рассматриваемых в части III
Процессор
Длина адреса, разряд
Размер регистра, разряд
Примечания. 1. При первичной разработке кристалла использовались только 24 раэ* ряда.
2. Управление памятью на том же кристалле расширит размер адреса до 20 разрядов.
Регистроваяreg
Косвенная регистровая@regили (reg)
Абсолютнаяехрг
Косвенная абсолютная@ехрг
reg
MEM[reg]
МЕМ [ехрг]
МЕМ [МЕМ [ехрг]]
Непосредственная#ехрг
ехрг
Автоинкрементная(reg) +
MEM[reg], reg := reg+1
Автодекрементная—(reg)
reg := reg—1, MEM[reg]
Относительная с исполь- expr(PCR)
MEM[ (expr—PLC) +PC]
зованием счетчика команд
По базе (или с индек- ехрг (reg)
MEM[expr+reg]
сированием) !>
MEM[regl +reg2]
По базе с индексирова- regl(reg2)
нием1)
Представление на
Способ адресацииязыке ассемблера
Операнд
Примечания. 1. Параметр ехрг представляет любое допустимое выражение.
2. При относительной адресации с использованием счетчика команд ассемблер вычисляет требуемое смещение между данным ехрг и текущим значением PLC (счетчика ячеек программы) поскольку доступ к МЕМ | ехрг | осуществляется во время ;прогона.
3. В некоторых случаях запись на языке ассемблера для ЭВМ 6809 фирмы Motorola отличается от общепринятой (см. гл. 17).
командах как процессора 6809, так и других процессоров обратитесь к соответствующим главам части III.
- Представление на языке ассемблера способов адресации, используемых в различных ЭВМ, довольно разнообразно. Некоторые способы адресации, реализованные в ЭВМ 6809, и соответствующие им представления на языке ассемблера показаны в табл. 7.2. Приведенный в таблице параметр ехрг является выражением, которое вычисляется во время трансляции и вставляется в команду. В гипотетической ЭВМ Н6809 параметр reg должен выражаться величиной X, в то время как в реальной ЭВМ 6809 указанный параметр может быть представлен любым индексным регистром. Аналогичные способы адресации свойственны большинству процессоров, содержащих регистры общего назначения, где параметр reg может представлять собой регистр общего назначения. Далее мы будем использовать термин адресный регистр для обозначения любого регистра, содержащего адрес.
Назначение способов адресации состоит в том, чтобы предоставить исполнительный адрес для команды. В команде обра-
Ч В отечественной литературе для данных способов адресации используются также соответственно термины «базовая адресация», «индексная адресация», «базовая индексная адресация». — Прим. ред.
ботки данных исполнительный адрес является адресом данных, над которыми выполняется операция. В команде перехода это адрес, по которому команда передает управление.
Способы адресации можно классифицировать на прямые и косвенные. При прямом способе адресации исполнительный адрес берется непосредственно из команды или вычисляется с использованием значения, указанного в команде, и содержимого какого-либо регистра. Косвенный способ -адресации предполагает, что в команде содержится значение косвенного адреса, т. е. адреса ячейки памяти, в которой находится окончательный- исполнительный адрес. В некоторых ЭВМ, таких, как 6809 и- PDP-11, каждому прямому способу адресации соответствует косвенный способ адресации. Другие ЭВМ, например Z8000 и 8086, имеют только косвенный способ адресации. 1
ОДНОКОМПОНЕНТНЫЕ СПОСОБЫ АДРЕСАЦИИ
При наиболее простых способах адресации исполнительный- адрес задается единственным значением в команде или регистре. При многокомпонентных способах адресации для получения исполнительного адреса комбинируют значение, указанное в команде, с содержимым одного или нескольких регистров.
Регистровая адресация
При регистровой адресации операнд хранится в одном из ре* гистров процессора. На рис. 7.1 представлено кодирование команды при регистровом способе адресации. На этом и других рисунках аббревиатура КОП обозначает поля команды, в которых содержатся код операции и другая информация, такая, как способ адресации и выбор слова/байта. На рисунках, помещенных в данной главе, не указывается размерность данных, адресов и других полей.
В ЭВМ Z8000 фирмы Zilog используется 4-разрядное поле в командах для выбора одного из 16 регистров общего назначения. В ЭВМ 6809 фирмы Motorola предусмотрены два аккумулятора и четыре индексных регистра; следовательно, во многих командах этой вычислительной машины также имеются поля выбора регистров. ЭВМ Н6809 располагает только одним аккумулятором и одним индексным регистром; какой именно регистр необходимо использовать, указывается неявно в коде операции, а способ адресации в этом случае называется неявным.
Все процессоры должны иметь регистровый способ адресации в той или иной форме, в противном случае было бы невозможно оперировать регистрами. Однако следует отметить,
что некоторые операции не имеют никакого отношения к регистровому способу адресации, например в случае использования команды JMP reg программа не может осуществить переход к регистру.
В программах, представленных на языке ассемблера, регистры обычно обозначаются зарезервированными идентификаторами (A,X,R0 и т. д.). Некоторые ассемблеры разрешают использование любого идентификатора для обозначения регистра при
Рис. 7.1. Регистровая адресация.
наличии специального префикса, чтобы отличать регистровые номера от адресов памяти (например, R0=%0, SP=%6 в ассемблере ЭВМ PDP-11).
Абсолютная адресация
Самым простым средством для указания полного адреса ячейки памяти является включение его в качестве составной части команды, т. е. использование абсолютной адресации (рис. 7.2). Мы использовали этот способ адресации в большинстве команд обращения к памяти ЭВМ Н6809 и Н8000 в примерах, представленных в гл. 5. Абсолютная адресация обеспечивает гибкость работы, позволяя указывать любую ячейку памяти в адресном пространстве ЭВМ.
При абсолютной адресации используется много разрядов команды ЭВМ, располагающей большим адресным пространством, например 32 разряда в ЭВМ 68000. Для того чтобы сократить это число разрядов, в некоторых ЭВМ применяется короткая абсолютная адресация, которая позволяет осуществлять прямой доступ к ограниченной части адресного пространства ЭВМ. Например, в ЭВМ 68000 обеспечивается способ адреса-
ции, при котором 16 разрядов в команде выделяются для указания адреса, а оставшиеся старшие разряды адреса принимают значения, равные знаку 16-разрядной части. Таким образом, становится возможно задавать любую ячейку памяти в верхней или нижней части адресного пространства объемом 32К байт. Когда в команде языка ассемблера определена абсолютная адресация, то ассемблер автоматически выбирает короткую абсо-
Рис. 7.2. Способы адресации:
а — длинная абсолютная; б — короткая абсолютная; в — косвенная абсолютная.
лютную адресацию, если адрес можно представить в 16 разрядах, используя правило расширения знака; в противном случае применяется длинная абсолютная адресация.
Благодаря простоте и гибкости абсолютная адресация используется почти во всех ЭВМ. Из микро-ЭВМ, рассматриваемых в этой книге, только ЭВМ MCS-48 не имеет длинной абсолютной адресации.
Косвенная абсолютная адресация находит применение только в нескольких типах ЭВМ, в том числе в ЭВМ 6809. Как показано на рис. 7.2,в, при этом способе в качестве исполнительного адреса используется содержимое ячейки памяти, задаваемой абсолютным адресом.
Непосредственная адресация
Этот способ адресации позволяет задавать константу (или литерал) как составную часть команды. Как показано на рис. 7.3,а и б, непосредственный операнд выбирается из памяти сра-
зу же за кодом операции команды. Команды ЭВМ PDP-11, 8086 и 6809 могут содержать 1- или 2-байтовые непосредственные операнды; команды ЭВМ Z8000 и 68000 могут иметь непосредственные операнды длиной до 4 байт. Программы, приведенные в предыдущих главах, дают представление о некоторых из многих возможных употреблениях непосредственных операндов (см. рис. 5.14):
колколОперандОперандКОП— Операнд —КОП— Операнд —l_£^ I z IОперандl_£^ I z IОперанд
кол
кол
Операнд
Операнд
КОП
— Операнд —
КОП
— Операнд —
l_£^ I z I
Операнд
l_£^ I z I
Операнд
а
Рис. 7.3. Форматы команд с непосредственной адресацией:
а — непосредственный операнд имеет такую же длину, как и код операции; б — длинная непосредственная адресация; в— короткая непосредственная адресация.
' • константы для инициализации переменных;
константы вместе с переменными, полученными в результате арифметических или логических операций (ADDA #1);
данные или адресные константы для сравнения с переменными с целью завершения просмотра списков и строк, для выполнения условного перехода и т. д. (СМРХ ^MASKE);
адресные константы, посылаемые в регистры для последующего использования при других способах адресации (LDS #STKE, LDX#MASKS).
За исключением адресных констант, большинство непосредственных операндов представляет собой небольшие целые величины со знаком, для размещения которых требуется 8 или менее разрядов. Некоторые процессоры используют 8-разряд- ные непосредственные величины с необходимым расширением знака до 16 или 32 разрядов. В вычислительных машинах Z8000 и 68000 используется короткая непосредственная адресация для некоторых команд (рис. 7.3,в). Например, команда Increment by п включает 4-разрядную величину для п в составе этой команды, имеющей длину в одно слово; при этом исключается необходимость использования дополнительного непосредственного слова. Некоторые команды, такие, как JUMP и STORE, не имеют никакого отношения к режиму работы с непосредственной адресацией.
Непосредственная адресация в действительности не является необходимым свойством архитектуры ЭВМ, поскольку константы можно всегда хранить в любых ячейках памяти и осуществлять к ним доступ с использованием абсолютной адреса-
щии, как и к другим данным. Например, вместо ADDA*! можно написать .
ADDA ONE
ONE FCB 1■
Однако использование непосредственной адресации уменьшает объем программы и сокращает время ее выполнения путем исключения неупорядоченных адресов констант. Другим важным моментом является то, что при этом способе адресации с программиста снимаются заботы по учету различных мелких деталей, таких, как присвоение неповторяющихся имен используемым константам.
Косвенная регистровая адресация
При косвенной регистровой адресации регистр или пара регистров содержат исполнительный адрес операнда. Вычислительные машины PDP-11, 9900 и Н8000 располагают 16-разряд- ными регистрами общего назначения и 16-разрядными адреса-
Номер
Рис. 7.4. Косвенная регистровая адресация:
а — длина адреса равна размеру регистра; б — длина адреса вдвое больше размера ре* гистра. "
ми; ЭВМ 6809 и Н6809 имеют 16-разрядные индексные регистры и 16-разрядные адреса, а ЭВМ 68000 — 32-разрядные регистры и 32-разрядные адреса. Во всех этих машинах один регистр можно использовать для указания любого адреса па- пяти (рис. 7.4,а).
ЭВМ Z8000 включает 16-разрядные регистры и может оперировать 31-разрядными адресами. В этом случае используется пара регистров, чтобы сформировать полный исполнительный
Рис. 7.5. Автоинкрементная адресация.
адрес с помощью косвенной регистровой адресации (рис. 7.4,6).
Все современные ЭВМ в том или ином виде используют указанный способ адресации. Это дает возможность вычислять адреса памяти во время выполнения программы, а не запоминать их при трансляции программы. Сравните, например, программу, представленную на рис. 5.9, с программой на рис. 5.10. Вычисление адреса во время прогона программы требуется для выполнения многих общих операций, таких, как передача параметров определенных типов в подпрограммы и обращение к произвольным данным в массивах, стеках, очередях, связанных списках и других структурах.
Автоинкрементная и автодекрементная адресация •
Косвенная регистровая адресация часто применяется для пошагового просмотра таблиц и списков данных, осуществляя последовательное обращение к каждому элементу данных и изменяя содержимое регистра таким образом, чтобы задавать очередной элемент данных, как это выполнено в программе инициализации массива (рис. 5.10). Автоинкрементная адресация обеспечивает вычисление исполнительного адреса, как и при косвенном регистровом способе, а затем автоматически увеличивает
содержимое регистра на размер операнда (рис. 7.5). Следовательно, указанная ниже последовательность (рис. 5.10)
ILOOP STA ©XУстановить компонент в нуль
ADDX #1Указать следующий компонент
может быть заменена командой
ILOOP STA (Х)+Произвести сброс компонента и установить
значение X для очередного компонента
Если обратиться к другому примеру (рис. 5.14), то здесь команда LDA @Х в цикле BLOOP становится командой LDA (Х)+, а команда ADDX #1 в процедуре BNO1 исключается.
Рис. 7.6. Автодекрементная адресация.
Величина, прибавляемая к содержимому регистра при автоинкрементной адресации, зависит от размера операнда. В ЭВМ с побайтовой адресацией содержимое регистра необходимо увеличивать на 1, чтобы указать адрес очередного байта; на 2 — для задания адреса следующего слова; на 4 — для указания адреса очередного двойного слова. Размер операнда можно определить, исходя из кода операции команды. Например, если используется команда Load byte auto-increment, то необходимо к содержимому регистра прибавить 1; если применяется команда Load word auto-increment, то следует к содержимому регистра прибавить 2.
Не во всех процессорах предусмотрена автоинкрементная адресация и не все размеры операндов легко определяются в процессорах, чтобы реализовать этот способ адресации. ЭВМ 68000 является наиболее гибкой в этом отношении и обеспечивает автоинкрементную адресацию байтов, слов и двойных слов. В ЭВМ PDP-11, 6809 и 9900 используется автоинкрементная адресация только для байтов и слов.
Автодекрементная адресация аналогична автоинкрементной и обеспечивает вычитание размера операнда из содержимого адресного регистра (рис. 7.6). Однако при этом способе адресации вычитание выполняется до использования содержимого регистра в качестве исполнительного адреса. В комбинации с автоинкрементной адресацией это дает возможность эффективно использовать любой адресный регистр в качестве указателя стека для стековой памяти. Следует отметить, что при этом соблюдаются следующие соглашения:
указатель стека всегда указывает на верхушку стека;
с уменьшением содержимого указателя стека объем стека увеличивается.
С учетом указанных соглашений автодекрементная адресация в ЭВМ 6809 может обеспечить передачу содержимого аккумулятора А в стек, используя значение X в качестве указателя стека следующим образом:
STA — (X)
Обращение к верхушке стека может быть также организовано с помощью косвенного регистрового способа адресации, например команда
LDA @Х
обеспечивает перезапись элемента данных из верхушки стека в аккумулятор. Наконец, элемент данных может быть извлечен из стека с помощью автоинкрементной адресации. Например, с помощью команды
ADDA (Х) +
осуществляется передача элемента данных из верхушки стека и его суммирование с содержимым аккумулятора. Обратите внимание, как в системе обозначений языка ассемблера выделяются предварительный автодекремент и последующий автоинкремент содержимого регистра. В ЭВМ 68000, 6809 и PDP-11 предусмотрен автодекрементный способ адресации, а в ЭВМ 9900 этот способ адресации отсутствует.
Вычислительные машины 6809 и PDP-11 имеют косвенную автоинкрементную и автодекрементную адресацию, т. е. указанные вычислительные машины располагают еще одним уровнем адресации. Как показано в гл. 13, косвенная автоинкрементная адресация, примененная к счетчику команд (который является одним из регистров общего назначения в ЭВМ PDP-11), приобретает новое качество — становится абсолютной адресацией. Однако косвенная автодекрементная адресация не нашла широкого распространения.
*
MAXLEN SIZE MINNUM MAXNUM NNUMS
*
STACKL STKEL •STACKH
STKEH *
SORT
PROCEDURE StackSort; {Based on Table 3-6}
EQU200CONST maxLen=200;
EQU201stackSize=201;
EQU-99minNum=-99;
EQU99maxNum=99;
RMB1VAR nNu^s,inNum : byte; spL,spH : address;
{Register usage: A=inNum; X=spL; Y=spH;}
RMBSIZEstackL, stackH :
EQUSTACKL+SIZE-1ARRAY [ 1..stacksize] OF byte;
RMBSIZE
EQUSTACKH+SIZE-1
BEGIN
LDX#STKELsp2;=MemAddress(stackL[stacksize]);
LDY#STKEHspH:-=MemAddress (stackH [stacksize]);
LDA#MINNUM-tJMEM[spL]:=minNum-1;
STA@X
LDA#MAXNUM+1BEM[spH]:=maxNum+1;
STA@Y
LDA#1nNums :=1;
STANNUMS
JSR WRMSG1’ Writein ('Input sequence: *);
JSRREADread (inNum);
WHILE!
CMPA #MINNUKWHILE inNum>=minNum
BLTWHILE4 1
CMPA #MAXNUHAND inNum<=maxNum DO
BGT WHILE4BEGIN
WHILE2
CMPA @XWHILE inNum < MEM[spL] DO
BGEWHILE3{Top of stackL —> stackH.)
LDB(X) +PushH (PopL) ;
STB- (Y)
BRA WHILES
WHILE3
CMPA @YWHILE inNum > MEM[spH] DO
BLEOUT3{Top of stackH —> stackL.)
LDB(Y) +PushL (PopH) ;
STB- (X)
BRA WHILE3
OUT3
STA- (X)PushL(inNum); *
JSRWRNUMwrite (inNum) ;
LDANNUMSnNums := nNums + 1;
ADDA#1
STANNUMS
IF!
CMPA #MAXLE®XF nNums. <= maxLen THEN
BGTELSE!
THEN!
JSRREADread(inNum)
BRAIFEND!ELSE BEGIN •
ELSE1
JSRWRMSG2writein(*** Too many inputs1);
LDAfMINNUM-1inNum ;= minNum - 1;
IFEND1
BRA WHILE1END;
END; {Inputs are now sorted.}
WHILE4
СМРХ
BEQ
#STKEL
OUT 4
WHILE spL <>
MemAddress(stackL[stacksize])
DO
LDB
(X) +
{Move everything into stackH.}
STB
-(Y)
PushH (PopL);
BRA
WHILE4
OUT4
JSR
WRMSG3
writein; write('Sorted sequence:
');
WHILES
CMPY
BEQ
#STKEH
OUTS
WHILE spH <>..
MemAddress(stackH[stacksize])
DO
LDA
(Y) +
{Print contents of stackH.}
JSR
WRNUM
write(PopH);
BRA
WHILES
OUT5
RTS
{Return to caller.}
END
SORT
END;
Рис. 7.7. Программа сортировки с использованием стеков.
На рис. 7.7 приведен пример программы с использованием автоинкрементной и автодекрементной адресации. Эта программа представлена на языке ассемблера ЭВМ 6809 и является версией программы сортировки на языке Паскаль (рис. 3.5). Программа использует аппаратные средства ЭВМ 6809, которые отсутствуют в ЭВМ Н6809:
второй аккумулятор В (функционирует так же, как и аккумулятор А);
второй индексный регистр Y (функционирует так же, как и индексный регистр X);
дополнительные разряды условий; устанавливаются в соответствующее состояние после выполнения каждой операции над данными, обеспечивая проверку отношений <, <=, > и > = для целых чисел в дополнительном коде с помопХью команд передачи управления BLT, ВЬЕ, ВОТ и ВОЕ соответственно. .
В программе, приведенной на рис. 7.7, индексные регистры X и Y используются в качестве указателей двух стеков, которые содержат числа, сортируемые в возрастающем и убывающем порядке. Для работы со стеками используются автоинкрементный и автодекрементный способы адресации. Программа вызывает пять подпрограмм для организации ввода-вывода информации: подпрограмма READ обеспечивает считывание числа на входе и помещает его в аккумулятор А; подпрограмма WRNUM выдает на печать значение целого числа, содержащегося в аккумуляторе А; с помощью подпрограмм WRMSG1, WRMSG2 и WRMSG3 осуществляется выдача на печать сообщений.
Вследствие того что стеки играют важную роль в программировании, большинство процессоров, в которых не реализована автоинкрементная и автодекрементная, адресация, располагают по крайней мере командами PUSH и POP. В ЭВМ Z8000 команды PUSH и POP могут использовать любой регистр в ка-
честве указателя стека, в то время как в вычислительной машине 8086 команды PUSH и POP оперируют выделенным для этой цели регистром SP (Stack Pointer). В ЭВМ MCS-48 не предусмотрены способы адресации или команды для посылки или выборки элемента данных за один шаг; в ЭВМ 9900 имеется автоинкрементная адресация (POP), но отсутствует автодекрементная адресация (PUSH).
Если автоинкрементную и автодекрементную адресацию можно использовать, оперируя любыми командами обращения к памяти, в том числе арифметическими, то команды PUSH и POP позволяют только передавать данные в стек и извлекать их из стека. Установив соглашения R0=A и R1=X, команду ADDA (X) + ЭВМ 6809 можно представить следующей совокупностью команд ЭВМ Z8000:
POP R2, @R1
ADD R0,R2
МНОГОКОМПОНЕНТНЫЕ СПОСОБЫ АДРЕСАЦИИ
Страничная адресация
Как показано на рис. 7.8, а, при страничной адресации вся память (в данном случае объемом 64К байт) делится на определенное количество равных по размеру страниц. Шестнадца-
Таблица 7.3. Страничные способы адресации
ЭВМ
Количество разрядов для адреса памяти
Количество разрядов для адреса на странице
Количество разрядов для номера страницы
Способ адресации
6809
16
8
8
Восьмиразрядный регистр страниц DPR, используемый командой
MCS-48 (память данных)
8
3
5
Одноразрядный регистр страниц выбирает страницу 0 или страницу 3
MCS-48 (память команд)
12
8
4
Условные переходы дают возможность перейти к требуемой ячейке памяти в текущей странице
тиразрядный абсолютный адрес может быть поделен на два элемента (рис. 7.8,6): 8-разрядный номер страницы указывает, какая из 256 страниц содержит требуемый байт информации; 8-разрядный адрес на странице задает местоположение байта в пределах 256-байтовой страницы.
Адрес(шестнадцатиАдрес(шестнадцати00000000158 7158 7ООСтраница ОСтраница О00FF 010000FF 0100Страница 1Страница 1O1FF 0200O1FF 0200б I/V? страницы IЛф^с на стр\б I/V? страницы IЛф^с на стр\Страница 2Страница 202FF02FFСчетчик команд (PC)N= страниц^I\N° страницы^дрес на стр [■ Исполнительный адресСчетчик команд (PC)N= страниц^I\N° страницы^дрес на стр [■ Исполнительный адресFF00FF00Регистр страницРегистр страницКОПКОПFFFFFFFFСтраница25"5Страница25"5| ^страниц^ Адреснастр.| ^страниц^ Адреснастр.ПамятьПамятьg |Л/£ страницы | Адрес на стр.■ Исполнительный адресg |Л/£ страницы | Адрес на стр.■ Исполнительный адресОперандОперанд
Адрес(шестнадцати
Адрес(шестнадцати
0000
0000
158 7
158 7
О
О
Страница О
Страница О
00FF 0100
00FF 0100
Страница 1
Страница 1
O1FF 0200
O1FF 0200
б I/V? страницы IЛф^с на стр\
б I/V? страницы IЛф^с на стр\
Страница 2
Страница 2
02FF
02FF
Счетчик команд (PC)
N= страниц^I
\N° страницы^дрес на стр [■ Исполнительный адрес
Счетчик команд (PC)
N= страниц^I
\N° страницы^дрес на стр [■ Исполнительный адрес
FF00
FF00
Регистр страниц
Регистр страниц
КОП
КОП
FFFF
FFFF
Страница25"5
Страница25"5
| ^страниц^ Адреснастр.
| ^страниц^ Адреснастр.
Память
Память
g |Л/£ страницы | Адрес на стр.■ Исполнительный адрес
g |Л/£ страницы | Адрес на стр.■ Исполнительный адрес
Операнд
Операнд
Рис. 7.8. Страничная адресация:
а — деление адресного пространства; б — адресные значения; в —• адресация с использованием базовой страницы; г — адресация с использованием текущей страницы; д — адресация с использованием регистра страниц.
Страничный способ адресации не имеет никакого отношения к физической организации памяти системы. Этот способ просто используется для указания адреса с помощью меньшего количества разрядов, чем это имеет место при однокомпонентных способах адресации. При страничном способе адресации команда содержит только адрес на странице (рис. 7.8, в, г, д); номер страницы определяется несколькими путями с помощью команды, формирующей длинный абсолютный адрес:
адресация по базовой странице. Номеру страницы присваивается нулевое значение, т. е. абсолютный адрес формируется из нулевой (базовой) страницы памяти;
адресация с использованием текущей страницы. Номер страницы' устанавливается равным значениям старших разрядов счетчика команд, при этом адрес располагается на той странице, где находится выполняемая команда (на текущей странице) ;
адресация с использованием регистра страниц. Номер страницы определяется по содержимому регистра страниц, в который предварительно программным путем загружается требуемый номер страницы.
Только несколько типов ЭВМ, описанных в данной книге, имеют страничную адресацию (табл. 7.3). Хотя при этом способе адресации уменьшается длина адреса для многих команд, все-таки иногда необходимо указывать полный адрес. Следовательно, ЭВМ, имеющие страничную адресацию, должны располагать и другими способами адресации, такими, как абсолютная и косвенная регистровая адресация.
Номер Регистры
Рис. 7.9. Вычисление адреса при адресации с индексированием.
Адресация с индексированием
При способе адресации с индексированием два компонента объединяются путем сложения. Этот способ является удобным средством организации доступа к массивам и таблицам. Как показано на рис. 7.9, составной частью команды является фиксированный базовый адрес (аналогично абсолютному способу адресации). Затем указываемое в специальном адресном регистре смещение (или индекс) прибавляется к базовому адресу, чтобы сформировать требуемый исполнительный адрес. Когда способ адресации с индексированием используется для доступа к массиву, то базовый адрес в команде соответствует базовому адресу массива, а значение в адресном регистре соответствует индексу массива.
Предположим, что в программе на языке Паскаль массив определен следующим образом:
VAR апаше : ARRAY [first., last] OF baseType;
Соответствующая программа на языке ассемблера должна зарезервировать для массива следующее количество байтов памяти: (last—first+l)n, где параметр п указывает размер в байтах переменной типа baseType. В обычных программах на языке Паскаль значениями параметра п являются 1, 2 и 4 соответственно для символьного, целочисленного и вещественного типов данных. Для ЭВМ 6809 4-байтовый массив определяется следующим образом:
ORG 1700Н
FIRST EQU 1CONST first=l;
LAST EQU 4last=4;
SCORE RMB LAST—FIRST+1 VAR score: ARRAY [first-last] OF byte;
В программе на языке ассемблера мнемоническому обозначению SCORE соответствует адрес первого компонента масси-
Адрес ^щестнадцат.)
Память
1700
score [ 1]
1701
score [2]
1702
score [3]
1703
score [4]
Рис. 7.10. Размещение массива SCORE [1...4] в памяти.
ва. Ячейки памяти при этом используются так, как показано на рис. 7.10. Следовательно, обозначение SCORE эквивалентно числу 1700Н.
Адрес компонента j в массиве определяется по формуле, называемой адресным полиномом. Для массива с именем SCORE [1...4] адресный полином имеет следующий вид:
Address (score [j]) = SCORE+j—1.
Адресный полином должен учитывать индекс первого компонента массива (first) и размер каждого компонента (п) массива. Как показано на рис. 7.11, если адресом компонента aname[first] является ANAME, то адресом aname[first+x] является ANAME+x-n, где п — размер каждого компонента. При
нимая соглашение j = first+x, получим общую формулу адресного полинома:
Address (aname [j]) =ANAME+(j—first) -п
= (ANAME—first • n) +j • n
= Effectives aseAd dress (aname) + j • n
Исполнительным базовым адресом (Effective Base Address) одномерного массива является адрес, назначенный нулевому ком-
Абрес Память
ANAME
^j> n байт
aname
~ [first] "
ANAME+п
aname
[f irst+1]
^> n байт
1NAME+ хп
aname
[fi rst+x]
^ n байт
Рис. 7.11. Адресация одномерного массива.
поненту массива, если имеется хотя бы один компонент. Как показано в скобках выше, исполнительный базовый адрес является постоянной частью адресного полинома.
Для массива с именем SCORE общая формула адресного полинома выглядит следующим образом:
Address (score [j]) «EffectiveBaseAddress (score) + j • 1
«(SCORE— l)+j
Таким образом, компонент j массива SCORE может быть считан в регистр А ЭВМ 6809 с помощью следующей последовательности команд (предполагается, что J является 16-раз- рядной переменной в памяти):
LDXJЧтение массива score [j]
LDASCORE—1 (X) Чтение массива score [j]
Выражение SCORE—1 представляет собой исполнительный базовый адрес массива; таким образом, величина 16FFH запоминается как базовый адрес в команде, использующей способ адресации с индексированием. На рис. 7.12 показана последовательность выполнения подобной команды, которая начинает выполняться с адреса 2000Н; компонент J хранится по адресу 2100Н. '
Операция умножения компонента j на п в адресном полиноме должна везде выполняться во время прогона программы, поскольку компонент j до этого момента не известен. Однако величина п всегда равна степени числа 2, поэтому умножение может быть" выполнено путем сложения или с использованием сдвигов. Например, мы можем объявить массив из 10 слов для
Рис: 7.12. Выполнение команды, использующей адресацию с индексированием.Рис: 7.12. Выполнение команды, использующей адресацию с индексированием.
Рис: 7.12. Выполнение команды, использующей адресацию с индексированием.
Рис: 7.12. Выполнение команды, использующей адресацию с индексированием.
ЭВМ 6809 и организовать доступ к компоненту j следующим образом:
FRST EQU 1LST EQU 10WORDS RMB (LST—FRST+1)*2EWORDSEQU WORDS— (2*FRST)FRST EQU 1LST EQU 10WORDS RMB (LST—FRST+1)*2EWORDSEQU WORDS— (2*FRST)LDX J ADDXJLDX EWORDS (X)LDX J ADDXJLDX EWORDS (X)CONST first = 1;last=10;VAR words: ARRAY [first.last] OF word; {Определить исполнительный базовый адрес}ЧтениеjУмножить на 2, чтобы получить смещениеX := words [j]CONST first = 1;last=10;VAR words: ARRAY [first.last] OF word; {Определить исполнительный базовый адрес}ЧтениеjУмножить на 2, чтобы получить смещениеX := words [j]
FRST EQU 1
LST EQU 10
WORDS RMB (LST—FRST+1)*2
EWORDSEQU WORDS— (2*FRST)
FRST EQU 1
LST EQU 10
WORDS RMB (LST—FRST+1)*2
EWORDSEQU WORDS— (2*FRST)
LDX J ADDXJ
LDX EWORDS (X)
LDX J ADDXJ
LDX EWORDS (X)
CONST first = 1;
last=10;
VAR words: ARRAY [first.last] OF word; {Определить исполнительный базовый адрес}
Чтениеj
Умножить на 2, чтобы получить смещение
X := words [j]
CONST first = 1;
last=10;
VAR words: ARRAY [first.last] OF word; {Определить исполнительный базовый адрес}
Чтениеj
Умножить на 2, чтобы получить смещение
X := words [j]
Подпрограмма, в которой для доступа к массиву используется способ адресации с индексированием, приведена на рис. 7.13. Программа осуществляет поиск простых чисел без
выполнения операций умножения или деления, составляя габ- лицу чисел, кратных известным простым числам массива. В программу включена новая команда АВХ, которая осуществ-
* Эта подпрограмма осуществляет поиск и печать всех
* простых чисел, находящихся з диапазоне от 2 до NPRIME, * используя решето Эратосфена.
*
*
PROCEDURE FindPrimes;
NPRIME EQU1000
CONST nPrime = 1000;
PLIMIT EQU32
pLimit = 32;
PRIME RMBNPRIME-1
*
VAR prime: ARRAY [2..nPrime] OF boolean; [reg] X : word; (reg) В : byte;
FNDPRM LDB| 1
BEGIN
LDX|2
FOR X ;= 2 TO nPrime DO
SETEM STBPRIME-2 (X)
prime [X] := true;
>ADDXI1
CMPX INPRIME BLESETEM
[Set the entire array true’.}
LDB#2
В := 2; {First known prime.}
MARKEM LDX#0
ABX
REPEAT {Check integers...}
X := B;- {...from 2 to pLimit.}
LDAPRIME-2(X)
IF prime[X] THEN
BEQNOTPRM.
ABX
BEGIN
X := X+B; {Mark multiples...}
CLRLUP CLRPRIME-2(X)
ABX
REPEAT {...of В as not prime.} prime[X] := false; X := X+B;
CMPX INPRIME
UNTIL X > nPrime;
BLECLRLUP
END;
NOTPRM INCB
В := B+1;
CMPB IPLIMIT BLE. MARKEM
UNTIL В > pLimit;
JSRWRMSG1
write ('Here are primes from 2 to ’);
LDXINPRIME
X := nPrime;
JSRPRINTX
writein (X); {Print the number in Xj
LDX12
FOR X := 2 TO nPrime DO
PRTLUP LDBPRIME-2(X)
{Print all the primes.}
BEQNEXTP
IF prime[X] THEN
JSRPRINTX
writein (X);
4EXTP ADDX |1 CMPX INPRIME BLEPRTLUP
RTS
{All done, return to caller}
ENDFNDPRM
END;
Рис. 7.13. Программа нахождения простых чисел с использованием массива и адресации с индексированием.
ляет сложение 8-разрядного числа без знака в аккумуляторе В с содержимым регистра X ЭВМ 6809.
Адресные полиномы могут быть также составлены и для многомерных массивов. На рис. 7.14 представлена строчная
матрица памяти для массива 3X4 байт. Адресный полином имеет вид
Address (matrix [j,к] )=MATRIX+(j—1)-4+(к— 1)
Данное определение двумерного массива
VAR aname [jf..jl,kf..kl] OF baseType;
предполагает, что адресный полином учитывает исполнительный базовый адрес массива (адрес компонента [0,0]), длину каж-
1700
1701
1702
1703
Пояснение: Адрес
1704
1705
1706
1707
1708
1709
170А
170В
СтолбецСтрока 1234
।23
Рис. 7.14. Строчная матрица памяти для массива [1..Д1...4].
дой строки ((kl—kf+l)*n) и размер каждого компонента (п):
Address (aname[j.k]) =ANAME+((j—jf) 1 (kl—kf +1) + (k—kf)) -n=
= (ANAME— (jf • (kl—kf +1) +kf) • n) + (j • (kl—kf+ +l)+k).n=
= EffectiveBaseAddress(aname) + (j- (kl—kf+l)+k) »n
Величина (ANAME—(jf-(kl—kf+l)+kf)-n) является исполнительным базовым адресом массива; эта величина записывается в команду, использующую способ адресации с индексированием, а остальная часть адресного полинома вычисляется
во время прогона, как показано ниже для массива 8X4 слов
(ЭВМ 6809):
BOXES RMB EBOXESEQU
JRMW
КRMW
ТЕМРХ RMW
4*8*2
BOXES—(4+1)
1
1
1
VAR boxes: ARRAY [1.Д1..4] OF word;
*2 {Определить исполнительный базовый адрес}
j,K : integer;
LDX ADDX STX ADDX ADDX STX ADDX LDX
J J ТЕМРХ ТЕМРХ К
ТЕМРХ ТЕМРХ EBOXES(X)
Чтение j
Умножить на длину строки (4)
Прибавить номер колонки
Умножить на 2, чтобы получить смещение
X := boxes [j,к]
Операция умножения необходима для вычисления адресов в многомерных массивах, поскольку длина строки обычно не является простой степенью числа 2. Однако операции умножения можно избежать, если использовать способ, называемый косвенной адресацией. При таком способе адресации применяется вспомогательная таблица адресов строк. Вместо вычисления начального адреса строки, получаемого как сумма исполнительного базового адреса и выражения j• (kl—kf+l)-n, ведут
J к
RMW RMW
1
1
VAR j, k : integer;
ROW1
RMB
10
cell : ARRAY [1..5,1..10] OF byte;
ROW2
RMB
10
ROW3
RMB
10
ROW4
RMB
10
ROW5
RMB
10
ROWTB
FCW
ROW1, ROW2, ROW3,
ROW4,ROW5
LDX
J
Read j and multiply
ADDX
J
by size of ROWTB components (2) •
LDX
ROWTB-2(X)
Get base add. ess of row.
ADDX
К
Add column number.
LDA
@х
A := cell [ j,k] .
Рис. 7.15. Использование косвенной адресации при обработке многомерного массива.
поиск, используя номер строки как индекс в таблице адресов строк. Затем к адресу строки прибавляется номер колонки, чтобы получить адрес требуемого компонента. Этот способ для массива 5X10, размещаемого в памяти ЭВМ 6809, представлен на рис. 7.15.
Большинство современных ЭВМ располагает нс меньше чем одним способом адресации с индексированием; из всех ЭВМ, описываемых в части III, только в ЭВМ MCS-48 отсутствует такой способ адресации. В некоторых ЭВМ предусмотрена косвенная адресация с индексированием, причем используются две ее разновидности:
прединдексная косвенная адресация. Смещение прибавляется к базовому адресу для формирования косвенного адреса; содержимое ячейки с косвенным адресом является исполнительным адресом;
пост индексная косвенная адресация. Базовый адрес используется в качестве косвенного адреса; смещение прибавляется к содержимому ячейки с косвенным адресом, чтобы полу-» чить требуемый исполнительный адрес.
Две вычислительные машины, описываемые в части III, имеют прединдексную косвенную адресацию — это ЭВМ PDP-11 и
6809. Процессор 6502 фирмы MOS Technology, относящийся к семейству ЭВМ 6809 фирмы Motorola, имеет как прединдекс- ную, так и постиндексную косвенную адресацию.
Адресация по базе -
Способ адресации по базе схож со способом адресации с индексированием, и по этой причине их часто путают. Базовый адрес всегда является полным адресом, в то время как смеще-
Рис. 7.16. Способ адресации по базе, применяемый в ЭВМ 6809.
ние может быть коротким или длинным. При способе адресации с индексированием команда содержит базовый адрес, а в индексном регистре находится смещение. При адресации по базе ситуация обратная — команда содержит смещение, а в адресном (или базовом) регистре размещен базовый адрес. Если смещения и базовые адреса имеют одинаковую длину, то оба способа адресации неразличимы. Их различие очевидно, когда используются короткие смещения.
На рис. 7.16 представлена адресация по базе, применяемая в ЭВМ 6809. Команда содержит 8-разрядное смещение со знаком, а в адресный регистр загружен 16-разрядный базовый адрес. Перед операцией сложения смещение дополняется за счет расширения знака до 16-разрядной величины со знаком.
ЭВМ 68000, Z8000 и 6809 имеют адресацию по базе. В ЭВМ с регистрами общего назначения любой регистр можно использовать в качестве базового, исключение иногда составляет регистр R0. В ЭВМ, использующей аккумуляторы, индексные регистры часто используются в качестве базовых регистров. Действительно, индексные регистры в ЭВМ 6809 следует назвать базовыми регистрами, так как они обеспечивают хранение 16-разрядных адресов, в то время как в командах могут указываться 5-, 8- или 16-разрядные величины смещений.
Адресация с индексированием реализуется в том случае, когда базовый адрес структуры данных известен во время
трансляции программы, а используемые компоненты требуют организации доступа во время выполнения программы, как это имеет место в большинстве случаев обработки массивов дан-
стустуРис. 7.17. Формат элементов списка с данными об именах и оценках
сту
сту
дентов.
ных. Адресация по базе применяется тогда, когда относительное положение элемента данных в структуре известно во вре- мя^трансляции, но начальный адрес структуры не известен.
^ULL •
EQU
0
End-of-list value.
LINK
EQU
0
Define format of list items.
NAME
EQU
2
SCORE1
EQU
22
SCORE2
EQU
23
SCORE3
EQU
24
*
SCOREI
LDX
HEAD
Point to first list item.
SCLOOP
CMPX
#NULL
End of list?
BEQ
OUT
If so, done.
CLRA
Else clear the three exam scores.
STA
SCORE 1 (X)
STA
SCORE2(X)
STA
SCORE3(X)
LDX
LINK (X)
Get address of next item.
BRA
SCLOOP
Go check it.
OUT
RTS
Done-, return»
Рис. 7.18. Подпрограмма инициализации списка.
Адресация по базе используется в следующих случаях:
для доступа к данным в параметрической области, базовый адрес которой передается в подпрограмму (см. разд. 9.3);
для доступа к данным, расположенным в стековой области; базовый регистр становится указателем кадра стека (см. разд. 5.4 и 9.3);
для доступа к данным и перехода к адресам в программе, нс зависящей от места размещения в памяти, когда базовый регистр загружается начальным адресом программы (разд. 7.4);
для доступа к конкретному полю блока в связанном списке посредством загрузки базового регистра начальным адресом блока.
В качестве примера для последнего случая использования адресации по базе предположим, что мы обрабатываем список имен студентов (NAME) и их экзаменационных оценок (SCORE), где каждый элемент списка имеет формат, приведенный на рис. 7.17. Теперь предположим, что некоторое количество таких элементов связано в некоторый список таким образом, что переменная HEAD содержит адрес первого элемента, поле LINK каждого элемента содержит адрес очередного элемента, а поле LINK нулевого элемента указывает конец списка. Затем можно использовать подпрограмму ЭВМ 6809, приведенную на рис. 7.18, чтобы установить поля экзаменационных оценок всех элементов списка в нулевое состояние. При каждом итерационном цикле регистр X содержит базовый адрес элемента, а смещения SCORE1, SCORE2 и SCORES используются для доступа к полям экзаменационных оценок.
Адресация по базе с индексированием
Вычисление адреса при адресации по базе с индексированием показано на рис. 7.19 для ЭВМ 6809; исполнительный адрес формируется путем сложения содержимого базового регистра с содержимым индексного регистра. В ЭВМ 6809 используется один из 16-разрядных индексных регистров в качестве базового регистра, значение индекса задается с помощью либо 8-разрядного аккумулятора (А или В), либо 16-разрядной пары аккумуляторов (А и В). Вычислительные машины Z8000 и 68000 также располагают способом адресации по базе с индексированием.
При адресации по базе с индексированием во время прогона программы обеспечивается вычисление как базового адреса структуры данных, так и смещения для элемента, содержащегося в структуре данных. Например, после того как программа вычислит базовый адрес таблицы или массива, она может загрузить его в базовый регистр, а затем осуществить доступ к произвольным элементам, индексы которых также вычисляются во время прогона программы.
Мы можем расширить подпрограмму SCORE 1 (рис. 7.18) таким образом, чтобы она осуществляла инициализацию поля NAME всего списка элементов, содержащих пробелы. Поскольку в поле NAME имеется только 20 символов, это может быть
б
Рис. 7.19. Адресация с индексированием в ЭВМ 6809:
л—1-байтовое смещение; б — 2-байтовое смещение.
SCOREI
SCLOOP
LDXHEADPoint to first list item.
CMPX#NULLEnd oflist?
BEQOUTIf so,done.
CLRAElse clear the three exam scores
STASCOR61(X)
STASCORE2 (X)
STASCORES(X)
LDA#20HASCII space.
LDB#NAMEStarting index of NAME field.
NLOOP
STAB(X)Store a space. .
ADDB#1Bump index to next NAME byte»
CMPB#SCORE1Finished entire field?
BNENLOOPContinue until all processed*
LDXLINK(X)Get address of next item.
BRASCLOOPGo check it.
OUT
RTSDone, return.
Рис. 7.20 Инициализация списка, включая поле имени.
реализовано перед командой LDX LINK(X) путем загрузки аккумулятора А символом пробела в коде ASCII и последующего выполнения 20 команд STA NAME + i(X). Однако последовательность команд будет значительно сокращена, если воспользоваться адресацией по базе с индексированием (рис. 7.20).
Относительная адресация
Адресацию, при реализации которой исполнительный адрес вычисляется как сумма фиксированного смещения в команде и текущего значения счетчика команд, называют относительной
а
Память
6
Рис. 7.21. Вычисление исполнительного адреса:
а — при короткой относительной адресации в ЭВМ 6809 и 8086; б — при длинной относительной адресации в ЭВМ PDP-11.
адресацией. При короткой относительной адресации используется смещение небольшой величины для указания адреса команд, находящихся недалеко от текущей команды, причем исключается использование дополнительных разрядов, необходимых для получения полного абсолютного адреса. На рис. 7.21, а показана короткая относительная адресация, используемая в ЭВМ 6809 и 8086. Команда содержит 8-разрядное смещение, которое расширяется по знаку до 16 разрядов, а затем складывается с содержимым счетчика команд для получения требуемого исполнительного адреса. Когда выполняется сложение с содержимым счетчика команд (PC), последний уже указывает очередную команду.
Команды языка ассемблера при относительной адресации обычно указывают абсолютный адрес, а нс смещение. Ассемб-
лер вычисляет требуемое смещение, исходя из текущего значения счетчика ячеек программы (PLC), и выдает сообщение об ошибке, если абсолютный адрес находится за пределами действия относительного способа адресации.
Наиболее широкое распространение короткая относительная адресация получила в командах перехода Branch или Jump. Хотя оператор GOTO не слишком часто используется в
S3 словаS3 слова
S3 слова
S3 слова
Рис. 7.22. Диапазоны адресов, доступные для выполнения относительных переходов:
^ — команды перехода в ЭВМ 6809 и 8086; б — команды перехода в ЭВМ PDP-11; в — команда SOB (Вычитание 1 и переход) в ЭВМ PDP-11.
эффективных программах на языке Паскаль, соответствующий примитивный оператор Branch крайне важен для формирования управляющих структур в программах на машинном языке. Операторы в программах на языке Паскаль, управляемые структурными операторами языка высокого уровня, обычно короткие. Из этого следует, что команды перехода в соответствующих программах на машинном языке обычно осуществляют переход к «ближайшим» адресам. Например, в одной из учебных программ ЭВМ 8080 фирмы Intel исполнительные адреса -более чем 80% команд перехода находились в пределах 127 байт от местоположения самой команды. Следовательно, при короткой относительной адресации в большинстве команд перехода можно указывать адрес, используя меньшее количество разрядов. На рис. 7.22, а показан диапазон адресов, доступных для команд Branch в ЭВМ 6809 и 8086.
Кроме ЭВМ 6809 и 8086, команды перехода с короткой относительной адресацией используются в ЭВМ PDP-11, Z8000, 68000 и 9900. Поскольку в этих ЭВМ команды должны начинаться с четных адресов, диапазон относительных переходов расширяется (рис. 7.22,6) путем умножения смещения на 2 пе-
ред прибавлением к содержимому счетчика команд1). Другой способ организации перехода используется командами, которые управляют циклами типа REPEAT, такими, как DJNZ (Decrement and Jump if Not Zero — Выполнить отрицательное приращение и переход, если не 0) в ЭВМ Z8000 и SOB (Sub- stract One and Branch — Вычитание 1 и переход) в ЭВМ PDP-11. Поскольку при такой управляющей структуре обычно требуется переход назад, смещение представляется как положительное число и вычитается из содержимого счетчика команд. В ЭВМ PDP-11 это обеспечивает диапазон 0—63 слова для команд перехода (рис. 7.22,в).
ЭВМ 68000, PDP-11, 8086 и 6809 также располагают длинной относительной адресацией с 16-разрядным смещением (рис. 7.21,6). Этот способ адресации можно использовать как в командах перехода, так и общих командах обработки данных. В ЭВМ 68000 длинная относительная адресация обеспечивает экономию разрядов адреса по сравнению с 32-разрядным абсолютным способом адресации. Длинный относительный способ адресации предпочтительнее и потому, что дает возможность создавать позиционно-независимые программы.
ПОЗИЦИОННО-НЕЗАВИСИМАЯ
ПРОГРАММА
Адрес загрузки является первым адресом, по которому программа загружается в память. Как было указано в разд. 6.4, трудно определять адреса загрузки в начале разработки большого программного проекта. Перемещающие ассемблеры и редактирующие загрузчики позволяют указывать адреса загрузки позже — во время процедуры загрузки. Почти во всех приложениях эти два программных средства очень важны. Однако даже они могут не помочь, когда программные модули размещаются в постоянном запоминающем устройстве (ПЗУ).
Когда программу помещают в ПЗУ, она сохраняется без изменения длительное время. Нельзя провести корректировку данных или команд, даже если известно, какие изменения необходимо внести. Предположим, например, что мы продаем ПЗУ объемом 4К байт, содержащее набор подпрограмм с плавающей точкой для ЭВМ 6809. Адрес загрузки такого ПЗУ фиксирован, когда разъемы ПЗУ подключены к плате памяти ЭВМ — различные разъемы соответствуют разным адресам загрузки. Проблема состоит в том, что различные пользовате-
*) За исключением ЭВМ. 68000, которая использует смещение в байтах. Этот неэффективный способ адресации был реализован в предположении, что в дальнейшем длина команды ЭВМ 68000 не будет ограничена четными номерами ячеек памяти.
ли почти всегда хотят подключить ПЗУ к разным разъемам ввиду того, что меняется конфигурация системы. Нам же не*- обходима программа, которая работает корректно при любом адресе загрузки. Такая программа называется статически позиционно-независимой.
В некоторых случаях полезно располагать возможностью перемещения программы в памяти после начала ее выполнения. Однако это не означает, что программисты отключают и переопределяют ПЗУ во время выполнения их программы. Более вероятно, это имеет место, когда программы выполняются под управлением операционной системы, ориентированной на обслуживание многих пользователей, и динамическое перемещение заданий в память и из памяти производится по мере необходимости. Как только программа оказывается в «зависшем» состоянии, она может быть перезагружена совершенно в другой участок памяти по сравнению с первоначальным. Программа, которая может быть перемещена аналогичным образом, называется динамически перемещаемой (или динамически позиционнонезависимой) программой.
Основной характеристикой позиционно-независимой программы является то, что в ней не содержится абсолютных адресов. Два способа адресации могут обеспечить позиционную независимость: относительный и базовый.
При относительной адресации смещение представляет собой разность между адресом команды и исполнительным адресом. Следовательно, команда и операнд могут быть выбраны каю единый блок и переданы в любую часть памяти без изменения значения смещения, хранимого в команде. Исполнительный адрес определяется как функция текущего значения счетчика команд во время выполнения конкретной команды. Следовательно, команды, которые используют этот способ адресации, являются динамически позиционно-независимыми.
Для достижения позиционной независимости программ может также использоваться способ адресации по базе. Программа организует вычисление собственного адреса загрузки после начала своего выполнения и помещает это значение в базовый регистр1). Как только адрес загрузки оказывается в базовом
и В ЭВМ 68000 и 6809 предусмотрена команда LEA (Load Effective Address — Загрузка исполнительного адреса), которая вычисляет исполнительный адрес согласно указанному способу адресации и помещает его в адресный регистр. Например, команда LEAX START(PCR) в ЭВМ 6809 вычисляет адрес метки START как функцию текущего значения счетчика команд и помещает его в базовый регистр X. В ЭВМ PDP-11 требуемый эффект может быть достигнут перезаписью содержимого счетчика команд в регистр с использованием команды MOV, а затем суммированием с небольшим смещением. В других ЭВМ для перезаписи содержимого счетчика команд в адресный регистр придется прибегнуть к изощренным приемам программирования.
регистре, все адресные ссылки в пределах программы могут выполняться с помощью способа адресации по базе со смещением относительно начала программы, как если бы программа была загружена, начиная с нулевого адреса. Поскольку адрес загрузки вычисляется только один раз во время загрузки, то команды, использующие этот способ адресации, являются статически позиционно-независимыми.
Соблюдение следующих правил позволит создать статически позиционно-независимые программы:
значения всех адресных констант должны вычисляться относительно адреса загрузки или значения счетчика команд во время выполнения программы, а не указываться с использованием непосредственной адресации. К значениям неадресных констант, аналогичных тем, которые используются для начальной установки счетчиков, выполнения сравнений и т. д., может быть организован доступ с использованием непосредственной адресации; .
доступ к другим локальным данным должен организовываться с использованием адресации по базе со ссылкой на адрес загрузки программы или относительной адресации со счетчиком команд. Абсолютная адресация не допускается;
все локальные переходы и вызовы подпрограмм необходимо организовывать с использованием адресации по базе со ссылкой на адрес загрузки программы или относительной адресации со счетчиком команд;
абсолютные адреса могут использоваться для обращения к абсолютным ячейкам. памяти за пределами позиционно-независимой программы (например, при вызове программы-утилиты операционной системы).
Правила достижения статически позиционно-независимых программ легче реализовать в одних ЭВМ. (68000), чем в других (6809, PDP-11)). Действительно, статически позиционно-независимую программу трудно написать для ЭВМ, в которой отсутствует относительная адресация со счетчиком команд или нет средств непосредственной загрузки содержимого счетчика команд в базовый регистр (Z8000, 9900, MCS-48). Поэтому возможность составлять статически позиционно-независимую программу для ряда применений является полезным качеством
процессора. Однако большинству процессоров это качество не свойственно.
Динамически позиционно-независимую программу можно получить путем использования относительной адресации со счетчиком команд. Однако в программе, которая манипулирует такими адресными величинами, как указатели, для получения динамической позиционной независимости требуется широкое применение адресации по базе. Всякий раз, когда выполняется перемещение программы, новый базовый адрес необходимо помещать в базовый регистр, а все ссылки к данным необходимо выполнять относительно этого регистра, используя адресацию по базе или с индексированием по базе. Кроме того, очень трудно при составлении программ ручным способом соблюдать правила получения динамически позиционно-независимой программы (см., например, упр. 7.10). Следовательно, динамическая позиционная независимость обычно достигается только в тех процессорах, которые имеют специальный базовый регистр. Этот регистр процессор неявным образом использует для всех обращений к памяти (например, ЭВМ 8086). Кроме того, динамическая позиционная независимость достигается в процессорах, в которых имеются блоки преобразования адресов памяти.
7.5. БЛОК ПРЕОБРАЗОВАНИЯ АДРЕСОВ ПАМЯТИ
Блок преобразования адресов памяти (MMU) добавляется к вычислительным системам, чтобы выполнять следующие функции:
увеличение объема основной памяти системы свыше того объема, который определяется архитектурой центрального процессора;
разделение большого объема основной памяти таким образом, чтобы можно было разместить в ней несколько программ без их взаимного случайного или преднамеренного воздействия друг на друга.
Удобно придерживаться следующего положения: для реализации первой функции прибегнуть к составлению карты распределения памяти, а для выполнения второй функции — к средствам управления памятью. Мы будем рассматривать эти два аспекта отдельно. Однако по мере их изучения мы убедимся, что они тесно связаны.
Составление карты распределения памяти
Как было указано в начале главы, одним из наиболее общих недочетов при проектировании процессора является отсутствие достаточного количества адресных разрядов для пред*
ставления большого объема памяти. Программные затраты пользователей для процессора, обеспечивающего работу с небольшим адресным пространством, направлены на то, чтобы найти пути увеличения используемого адресного пространства имеющейся ЭВМ вместо переписывания всего программного обеспечения для другой ЭВМ, располагающей большим адрес-
Рис. 7.23. Подключение блока MMU к вычислительной системе.
ным пространством. Следовательно, блоки преобразования адресов памяти спроектированы и созданы для того, чтобы увеличить объем основной памяти, которая может быть подсоединена к процессорам, обеспечивающим работу с относительно небольшими адресными пространствами, таким, как PDP-11 и 6809 (каждая ЭВМ располагает объемом памяти 64К байт).
Подключение блока MMU к вычислительной системе показано на рис. 7.23. В блок MMU поступает каждый т-разрядный адрес от процессора и преобразуется в n-разрядный адрес памяти, причем п^ш. При этом m-разрядный адрес от процессора называется логическим адресом, а n-разрядный адрес — физическим адресом. Логико-физическое преобразование выполняется под управлением «карты» в блоке MMU, как будет пояснено ниже.
Блок MMU подключен также к шине ввода-вывода процессора. Процессор может считывать элементы блока MMU и записывать их туда, используя обычные команды ввода-вывода, как если бы этот блок состоял из набора портов ввода-вывода. Интерфейс организован менее сложно, если ЭВМ использует карту памяти для ввода-вывода, при которой"может потребоваться специальная карта (по умолчанию) для доступа к «портам ввода-вывода» блока MMU. (См. разд. 10.1 и 10.2 и источники, указанные в литературе к данной главе, где обсуждаются соответствующие концепции ввода-вывода). В любом случае процессор управляет процессом преобразования, поскольку по
мере необходимости он может изменять карту распределения памяти.
Используя карту распределения памяти, программа может осуществлять при одном обращении доступ к тому объему физической памяти, который позволяет базовая архитектура процессора, например к 64К байт в ЭВМ PDP-11 или 6809. Однако, изменяя карту, программа может получать доступ к раз*
Рис. 7.24. Простая карта логико-физического преобразования адресов в блоке мми.
личным областям физической памяти в разные моменты времени. Следовательно, программа, которая манипулирует очень большим объемом данных, может разделить структуру данных на области объемом 64К байт или меньше и определенным образом изменить карту, чтобы осуществить доступ к различным данным.
Другое широкое использование карты распределения памяти имеет место в операционных системах, обслуживающих большое число пользователей. Каждая программа пользователя может задействовать все пространство логических адресов ЭВМ (например, 64К байт), хотя в это же время операционная система может выделять разные области физической памяти для каждого пользователя. В этом случае вся ответственность по установке карты возлагается на операционную систему всякий раз, когда выполняется соответствующая программа пользователя. При таком использовании функция управления памятью имеет большое значение — она обеспечивает «защиту» программ различных пользователей от взаимного влияния.
В качестве примера на рис. 7.24 приведена простая карта блока MMU, для которой предполагается т=16 и п=20. Блок MMU преобразовывает 64К-байтовое пространство логических адресов процессора в пространство физических адресов объ-
Рис. 7.25. Логико-физическая карта со страничной организацией памяти, сформированная блоком MMU.
емом 1М. байт. Без всякой модификации через блок MMU передаются 13 младших разрядов логического адреса. Три старших разряда используются в качестве индекса в карте, которая имеет 8 рядов. Семиразрядное число в выбранном ряду присоединяется к 13 младшим разрядам логического адреса, формируя 20-разрядный физический адрес.
Как показано на рис. 7.25, блок MMU обеспечивает деление пространства как физических, так и логических адресов на страницы объемом по 8 К байт. В любое время адреса в каждой логической странице преобразуются в соответствующие адреса в физической странице, указываемой строкой карты. Хотя отсутствуют средства, которые предотвратили бы преобразование двух или более логических страниц в одну и ту же физическую страницу, обычно различные логические страницы преобразуются в различные физические страницы. В любом случае программа в данный момент времени может осуществить доступ только к части физической памяти. Чтобы осуществить доступ к физической странице, которая не имеет логической страницы в существующей карте, программа должна изменить карту.
Более сложные блоки MMU допускают использование логической страницы, которая меньше полной страницы физической памяти, и могут осуществить лучший выбор границ для физических страниц. В этом случае для программы нет необходимости использовать физическую страницу максимального размера, что позволяет более рационально распределять память в ЭВМ.
Управление памятью
Средства управления памятью позволяют нескольким различным программам («задачам» или «процессам») использовать одну и ту же физическую память, не оказывая влияния друг на друга. В качестве примера рассмотрим блок MMU, который мы описали в предыдущем разделе. Физическая память объемом 1М байт может обеспечить хранение операционной системы объемом 64К байт и пятнадцати независимых программ пользователей объемом 64К байт каждая. Перед выполнением конкретной программы процессор может установить блоку MM.U задание — преобразовать 64К-байтовое пространство логических адресов процессора в 64К-байтовое пространство физических адресов, зарезервированных для этой программы. Программы не могут оказывать влияние на работу друг друга, поскольку каждая из них осуществляет доступ только к своей 64К-байтовой области физической памяти, как это определяется картой.
Действительно, программа может быть даже защищена от нежелательных собственных действий. Например, на рис. 7.24 показан лишний разряд, который имеется в каждом элементе карты. Совместно с соответствующими электронными схемами внутри блока MMU и шиной управления памятью разряд W может быть использован для «защиты по записи» выбранных
страниц памяти. Когда этот разряд установлен для конкретной логической страницы, процессор нс может осуществить случайные перезаписи команд или данных в этой странице. Более сложные по структуре блоки MMU могут предоставлять другие формы контроля по доступу к памяти, такие, как «только выполнение операций» для страниц, содержащих команды. В этом случае процессор может только выполнять операции, но нс считывать их как данные. В системе, обслуживающей большое число пользователей, это позволит одному пользователю обращаться к программе другого пользователя без возможности ее копирования.
Читатели, которых удовлетворяют операционные системы с подобными ограничениями, могут задаться вопросом: «Почему моя программа не может просто изменить карту, чтобы получить доступ к физическим страницам другой программы?» Действительно, это вполне реальная возможность, если не принять необходимых мер предосторожности при разработке аппаратных и программных средств. На практике может быть использовано несколько различных подходов к решению этой проблемы. Однако обычно приходят к следующему решению: в блоке MMU должны находиться по крайней мере две отдельные карты: одна для операционной системы, а другая (или несколько других карт) для программ пользователей. Дополнительно процессор может быть сконструирован для двух режимов работы — пользователя и супервизора.
При работе в режиме супервизора из блока MMU выбирается карта супервизора и процессор может выполнять любую команду из набора команд данной вычислительной системы. При реализации режима пользователя из блока MMU выбирается карта пользователя и процессор не может выполнять никакой команды, находящейся в заранее определенном списке «привилегированных» команд. Это команды, которые могут нарушить целостность операционной системы.
Список «привилегированных» команд обычно включает все команды ввода-вывода. Операционная система всегда переводит центральный процессор в режим пользователя, как только управление передастся программе пользователя. Следовательно, если карты MMU являются доступными для команд ввода-вывода, то программа пользователя не может иметь доступ к картам. Конечно, все другие операции ввода-вывода также должны выполняться под управлением операционной системы, поскольку только она может реализовать команды ввода-вывода.
В системе, в которой операции ввода-вывода выполняются с использованием карты памяти (см. разд. 10.2), отсутствуют «привилегированные» команды ввода-вывода, а порты ввода-вывода блока MMU находятся в том же пространстве физиче-
ских адресов, что и вся остальная память. В этом случае необходимо убедиться только в одном — что невозможно обратиться к адресам портов ввода-вывода блока MMU через страницу в карте пользователя. Программа пользователя может получить доступ к другим портам ввода-вывода, но никогда к портам ввода-вывода блока MMU.
Для обеспечения достаточной эффективности управления памятью необходимо решить несколько проблем:
Каким образом провести начальную установку аппаратных и программных средств управления памятью?
При первичной подаче питания на ЭВМ карты блока MMU пусты или содержат некорректную информацию. Следовательно, аппаратные средства должны перевести процессор в режим супервизора и предоставить в его распоряжение логико-физическую карту, назначаемую по умолчанию. В этом случае процессор может исполнять программу предварительного ввода из ПЗУ в страницу (страницы), назначаемую по умолчанию, чтобы сформировать необходимые карты.
Если программы пользователей не имеют доступа к страницам операционной системы и не могут изменить операционный режим процессора или карту, то каким образом они могут передать управление операционной системе?
В этом случае проблема решается с помощью аппаратных средств. Процессор должен располагать специальными командами, такими, как программные прерывания или системные вызовы (см. разд. 11.3.2), которые автоматически устанавливают режим супервизора и осуществляют переход к подпрограмме в адресном пространстве операционной системы, как определено картой супервизора.
Что должно случиться при поступлении сигналов прерываний?
Аппаратные средства должны автоматически переключаться в режим супервизора и использовать карту супервизора, когда имеет место прерывание процесса. Следовательно, все прерывания обрабатываются (по крайней мере первоначально) программами обслуживания, находящимися в адресном пространстве операционной системы.
Управление памятью налагает много ограничений на организацию доступа к памяти, на выполнение и использование привилегированных команд и т. д. Что произойдет, если программа нарушит одно из этих ограничений?
Аппаратные средства процессора и блока MMU должны обнаруживать такие нарушения защиты. Если имеет место нарушение, то необходимо сформировать прерывание. Подобно другим программам обслуживания прерываний, программа обработки этого прерывания должна находиться в адресном пространстве операционной системы.
Что произойдет, если указатель стека не указывает действительную область стека, когда происходит прерывание? Лучший случай-, не будут ли утеряны адрес возврата и информация о состоянии процессора? Худший случай1, не прекратит ли процессор формировать при последующем прерывании сообщения о нарушении защиты при попытках обращения к несуществующей или защищенной области памяти?
Эта проблема решается аппаратными средствами. Процессор должен иметь два указателя стека: один для пользователя, и другой для операционной системы. В режиме пользователя процессор всегда оперирует указателем стека пользователя и последний не в состоянии обращаться к указателю стека супервизора. В режиме супервизора процессор всегда оперирует указателем стека супервизора: информация о прерывании передается в стек супервизора. Следовательно, необходимо, чтобы операционная система проверяла достоверность состояния своего указателя стека всякий раз, когда она передает управление программе пользователя.
Каким образом можно передавать данные между областями логических адресов различных программ в тех случаях, когда требуется совместное использование данных?
Операционная система может всегда формировать собственную карту для записи данных в любые требуемые физические страницы (и для считывания с этих страниц). Следовательно, программы пользователей могут вызывать операционную систему для выполнения межпрограммных передач информации под управлением операционной системы.
Каким образом выполняется передача данных при прямом доступе к памяти (ПДП)?
При одном подходе аппаратные средства обеспечивают исключение блока MMU из работы при передаче информации в режиме прямого доступа к памяти; при этом интерфейс ПДП должен взаимодействовать с физическими адресами. При реализации другого подхода должна присутствовать не менее чем одна дополнительная карта в блоке MMU для организации передачи информации в режиме ПДП; при этом аппаратные средства автоматически выбирают эту карту. В этом случае интерфейс ПДП организует взаимодействие только с логическими адресами.
Предложенные варианты решения поставленных проблем не являются единственно возможными. Существуют другие варианты в проектировании и использовании блоков управления памятью и операционных систем. Поскольку детальное обсуждение этих проблем выходит за рамки данного раздела, для более глубокого изучения этих вопросов обратитесь к приведенным ниже источникам.
. УПРАЖНЕНИЯ
Перепишите программу, приведенную на рис. 7 13, используя вместо адресации с индексированием автоинкрементный способ.
Во многих ассемблерах не допускается использование круглых скобок в выражениях операнда и все операции имеют одинаковое старшинство (т. е. выражение вычисляется строго слева направо). Перепишите определение массива WORDS (разд. 7.3.2) для ассемблера такого типа.
Исполнительный базовый адрес двумерного массива, определенного в разд. 7.3.2, в действительности не является адресом компонента [0,0]. Объясните почему.
Какие факторы ограничивают максимальное значение величины NPRIME, используемой в подпрограмме нахождения простых чисел (рис. 7.13)?
Напишите программу на языке ассемблера, моделирующую «жизнь» (Life) по Конвею. Дан прямоугольный массив ячеек памяти, каждая из которых может содержать организм. За исключением границ массива, каждая ячейка может иметь восемь ячеек, непосредственно примыкающих к ней. Выживание и воспроизводство организмов из поколения в поколение зависит от числа соседей и определяются следующими простыми правилами:
если организм имеет только одного соседа или вообще их не имеет, то он умирает от одиночества;
если у организма два или три соседа, то он выживает до очередного поколения;
если число соседей организма более четырех, то он умирает от перенаселенности:
организм рождается в любой пустой ячейке, которая имеет три соседа.
Все изменения происходят одновременно; судьба организма определяется текущим поколением и не зависит от того, что может случиться с его соседями в следующем поколении. Следовательно, игра может моделироваться программой, в которой исполрзуются два массива CURG и TEMPG размером шХп байт. Каждый компонент массива содержит букву О в коде ASCII, если ячейка содержит организм, в противном случае — пробел.
Для каждой ячейки в массиве CURG программа осуществляет проверку соседних ячеек и помещает следующее поколение, возникающее в соответствующей ячейке, в массив TEMPG. (Граничная обработка может быть упрощена путем использования массива m+2Xn+2, в котором границы после начальной установки всегда содержат пробелы.) После обработки всех ячеек в массиве CURG программа может копировать массив TEMPG в массив CURG или просто выполнять замену массивов CURG и TEMPG.
Размер массива т+п может соответствовать размеру экрана электронно-лучевой трубки дисплея, например 24X80. В гл. 10 мы продолжим обсуждение этой задачи и рассмотрим способы отображения поколений на дисплее. 7.6. Перепишите подпрограмму подсчета единиц (рис. 5.14) для ЭВМ 6809 яли другого процессора. Новая версия подпрограммы должна содержать таблицу N1S[O...255] объемом 256 байт, инициализируемую во время загрузки, где элемент i таблицы NlS[i] содержит количество единиц в двоичной системе счисления. Подпрограмма BCNT1S должна просматривать количество единиц во входном числе, используя адресацию с индексированием.
,/Модифицируйте подпрограмму преобразования двоичных чисел в код ASCII, представленную в упр. 5.13, таким образом, чтобы символы результата не хранились в фиксированных ячейках памяти. Обеспечьте, чтобы при вызове подпрограммы в регистре X содержался базовый адрес массива ASC [0...2] размером 3 байт, где должны храниться символы результата.
Для большинства процессоров очень трудно составить позиционно-независимую программу из-за невозможности выборки для программы текущего значения счетчика команд; действительно, в микро-ЭВМ Н6809 это выполнить невозможно. Мы могли бы определить команду TFR РС,Х для микро-ЭВМ Н6809 для передачи содержимого счетчика команд в регистр X. Однако
предположим, что определена только команда TFR S,X для передачи текущего значения указателя стека в регистр X. Составьте программу TFRPCX, которая вычисляет и помещает в регистр X адрес команды, следующей за командой JSR TFRPCX.
Объясните, почему приведенный ниже фрагмент программы является статически позиционно-независимой программой.
FOR
RMB
20
Объем памяти в байтах для массива FOR [1.20]
I
RMB
1
Память для индекса в массиве FOR
TFR
PC,X
(или команда JSR TFRPCX из предыдущего упражнения)
HERE
ADDX#FOR—1-
-HERE
LDB
I(PCR)
LDA
B(X)
А: = FOR[I]
7.10. Объясните, почему приведенный выше фрагмент программы не является динамически позиционно-независимым.
ЛИТЕРАТУРА
Мнение Белла и Мюджа о том, что большое адресное пространство важно для перспективной и сложной архитектуры ЭВМ, получило широкое распространение. Этот вопрос освещен в статьях разработчиков ЭВМ Z8000 фирмы Zilog и ЭВМ 68000 фирмы Motorola \Computer, 12, 2 (Febr. 1979)]. Некоторые проектировщики не согласны с тем значением, которое придается различным способам адресации, имеющимся в ЭВМ. Например, тогда как разработчики ЭВМ 68000 считают, что наличие автоинкрементного и автодекрементного способов адресации открыло наилучший путь к манипулированию стеками, а также созданию элементарных операций по пересылке и поиску непрерывных блоков данных, создатели ЭВМ Z8000 решили сократить лишние способы адресации и разработали специальные команды PUSH, POP для обслуживания стеков и команды по пересылке блоков и сравнению данных.
Другим моментом, по которому возникли разногласия, является разработка способов адресации, обеспечивающих создание позиционно-независимых программ. Разработка таких способов адресации считалась «одной из важнейших целей проектирования» разработчиков ЭВМ 6809 фирмы Motorola [Ritter, Boney, A Microprocessor for the Revolution: The 6809, Byte, 4, 1, 34 (Jan. 1979)]. Другие разработчики не придают особого значения составлению статически позиционно-независимых программ для ЭВМ 6809. Вместо этого они предлагают методы управления памятью и составления карты распределения памяти, что позволяет получить динамически позиционно-независимые программы (Реи- to В., Stone Н., частные сообщения). Различные мнения возникли вследствие того, что проектировщики концентрировали внимание на различных приложениях; статически позиционно-независимая программа для ЭВМ 6809 очень эффективна для «консервированного» программного обеспечения в постоянных запоминающих устройствах, в то время как динамически позиционно-независимая программа, обеспечиваемая составлением карты памяти и управлением памяти, нужна для мультипрограммных систем, которые могут выполнять программу в различных областях физической памяти в разные моменты времени.
Мы только коснулись вопросов составления карты распределения памяти и управления памятью и их связи с операционной системой. Более подробно эти вопросы рассмотрены в труде, выпущенном под редакцией Г. Стоуна (Introduction to Computer Architecture, ed. by H. Stone, SRA, 2nd ed., 1980). Вопросы защиты информации на различных уровнях — логической и физической памяти, процессора и др. — довольно полно освещены в книге Р. Уотсона (Watson R. W., Timesharing System Design Concepts, McGraw-Hill, 1970).
88
8
8
ОПЕРАЦИИ
Как показано в гл. 5, каждому типу организации процессоров соответствует свой формат команд; процессоры, приведенные в части Ш, имеют и другие более существенные отличительные черты. Однако операции, выполняемые процессорами разного типа, имеют много общего, что обусловлено историческими, а чаще всего практическими причинами. Рассматривая компромиссы, на которые пришлось пойти при создании микропроцессора Z8000, Пётб писал1*: «Иметь бодьшое число кодов операций, конечно, очень важно: реализация определенной команды аппаратным способом приводит к экономии памяти и увеличению быстродействия. Однако обычно все же требуется больше внимания уделять полноте набора операций, ориентированных на определенный тип данных, а не дальнейшему увеличению числа специальных команд».
Данная глава посвящена рассмотрению всевозможных операций, выполняемых процессорами типичных ЭВМ, за исключением операций ввода-вывода, которые приведены в гл. 10. Несмотря на то что здесь описан «достаточно полный набор» операций, операции отдельных процессоров не рассмотрены; наилучшим источником информации по этому вопросу является литература, выпускаемая фирмами-изготовителями.
Для каждой операции в данной главе приведены обычно употребляемые мнемонические обозначения. Однако следует помнить, что для одних и тех же операций различные фирмы используют разные мнемонические обозначения, в то время как одинаковые обозначения в различных ЭВМ могут иметь разный смысл. Мнемонические обозначения, используемые фирмами, будут приведены в части III при описании реальных вычислительных машин; тем не менее для ознакомления с особенностями, которые не нашли отражения в данной книге, лучше всего использовать руководства по применению ЭВМ, выпускаемые фирмами-изготовителями.
° Peuto В. L, Architecture of a New Microprocessor, Computer, 12, 2, 10— 21 (Febr. 1979).
Каждая операция в данной главе описывается с помощью соответствующего оператора расширенного языка Паскаль. Для такого описания очень важны некоторые особенности этого языка, которые перечислены ниже:
Если X является переменной типа массив битов, та X [bnum] обозначает бит bnum этого массива.
Стандартная функция Badd (х,у : bitarray; с : bit) позволяет производить сложение n-битовых массивов х и у с начальным переносом с и получать n-битовый результат, устанавливая глобальные переменные ВС в BV в соответствии с переносом из старшего разряда и переполнением, возникающим при операциях над числами в дополнительном коде.
Стандартная функция Bcom(x : bitarray) обеспечивает получение инверсного значения для каждого бита п-битового массива х.
Стандартные функции Band (х,у: bitarray), Bor (х,у : bitarray) и Bxor (х,у : bitarray) позволяют производить логические операции И, ИЛИ и Исключающее ИЛИ над двумя n-битовыми массивами х и у и получать n-битовый результат.
ФОРМАТЫ КОМАНД
Операции над данными, рассматриваемые в последующих разделах, можно классифицировать по числу операндов; наиболее часто используются команды с одним и двумя операндами. Команды с одним операндом имеют следующий формат:
Код операции Операнд Описание
OP dst dst:=F(dst);
С помощью таких команд в общем случае производится считывание величины операнда приемника (места назначения) dst, выполнение над ним операции и запись результата в dst. В зависимости от конкретного процессора и команды dst может представлять собой или регистр, или ячейку памяти, которые задаются с помощью одного из разрешенных способов адресации.
В большинстве процессоров формат команд с двумя операндами выглядит следующим образом:
Код операции Операнды Описание
OP dst,src dst := F(src,dst);
Реализация таких команд обычно состоит в считывании величин операнда приемника dst и операнда источника sre, выполнении над ними какой-либо операции и записи результата в dst. Частным случаем таких команд являются команды пере- 19*
сылки, которые обеспечивают копирование величины src в dst.
В соответствии с правилами ведения документации в программах, написанных на языке ассемблера, для всех команд процессоров PDP-11 и 68000, а также для некоторых команд процессора 9900 принят обратный порядок следования src и dst:
OP src,dst dst := F(src,dst);
Допустимая идентичность src и dst в командах с двумя операндами определяется архитектурой процессора. В одноадресных (построенных на основе аккумулятора) процессорах, таких, как 6809 или MCS-48, dst — это обычно аккумулятор, a src — регистр или ячейка памяти, которые задают с помощью одного из способов адресации:
OP accum,src accum := F(src,accum);
Подобно этому, в полутораадресных командах, имеющихся у процессоров с регистрами общего назначения, например Z8000, 68000, 9900 или 8086, dst обычно представляет собой один из регистров, в то время как src может быть любым регистром или ячейкой памяти, которую задают с помощью одного из разрешенных способов адресации:
OP reg,src reg :=F(src,reg);
Некоторые команды могут иметь также «инверсную» форму записи, при которой src является регистром, a dst есть регистр или ячейка памяти:
OP dst,reg dst := F(reg,dst);
В двухадресных командах процессоров PDP-11, 68000 и 9900 src и dst оба могут быть или регистрами, или ячейками памяти, заданными с помощью какого-либо способа адресации; только такие команды позволяют выполнять операции типа память- память.
Большинство 16-. и 32-разрядных процессоров позволяют производить операции и над байтами. Поэтому в типичных командах пересылки и обработки данных наряду с операцией задается также длина операнда (или операндов). В командах с двумя операндами операнды источника src и приемника dst должны иметь одинаковую длину. В языке ассемблера длина операндов обычно указывается с помощью специального индекса (суффикса) в мнемоническом обозначении команд. Например, в процессоре Z8000 команды ADD, ADDL и ADDB позволяют производить соответственно сложение слов, длинных слов и байтов.
РАЗРЯДЫ УСЛОВИЙ
В некоторых процессорах, построенных на основе аккумулятора, например MCS-48, по команде условного перехода производится проверка содержимого аккумулятора и в соответствии с определенным условием (например, нулевое/ненулевое, поло- жительное/отрицательное значение) осуществляется переход. В процессорах с регистрами общего назначения для выполнения такого перехода необходимо, чтобы команда перехода задавала не только условие и адрес перехода, но и регистр, подлежащий проверке. В программах вычислительных машин команды условного перехода обычно составляют значительную часть (до 20%) от всех используемых команд, и поэтому очень важно оптимизировать их длину и эффективность выполнения. Следует отметить, что именно с помощью таких команд чаще всего производится проверка операнда, над которым только что была выполнена операция. Преимущество в этом отношении имеют процессоры, в которых используются разряды условий.
Разряды условий {коды условий или флажки) представляют собой совокупность отдельных разрядов состояния, автоматически устанавливаемых процессором в соответствии с результатом выполнения каждой команды. Например, всякий раз, когда при выполнении операции получается нулевой результат, разряд Z во всех процессорах, рассмотренных в гл. 5, устанавливается в 1.
По команде условного перехода производится проверка одного или нескольких разрядов условий, и при их определенном значении осуществляется переход к новой ячейке памяти. В процессорах, рассмотренных в гл. 5, единственным разрядом условий является разряд Z, поэтому в них использовались только такие команды, как «Переход, если равно» и «Переход, если нс равно». Однако, как следует из программы сортировки (рис. 7.7), существуют и другие условия, подлежащие проверке: «больше чем» и «меньше чем». Для выявления таких соотношений необходимо иметь дополнительные разряды условий.
Если говорить о числе, названии и назначении разрядов условий, то они у разных процессоров различны. Тем нс менее наиболее распространенным является набор разрядов условий, используемый в процессорах PDP-11, 6809, 68000, Z8000 и 8086.
N — знак, отрицательный результат. Значение этого разряда совпадает со значением старшего (знакового) разряда результата.
Z — нуль. Разряд Z устанавливается в 1, если все разряды результата имеют нулевое значение; в противном случае принимает нулевое значение.
V — переполнение. Разряд V устанавливается в 1, если при выполнении арифметических операций над числами, пред-
ставленными в дополнительном коде, возникает переполнение. Когда в соответствии с программой производится сложение или вычитание n-разрядных чисел со знаком, равенство V=1 указывает на то, что истинный результат больше чем 2П-1—1 или меньше чем —2П-1. Выражение N XOR V (N Исключающее ИЛИ V) всегда даст правильный знак результата в дополнительном коде, так как равенство V=1 указывает на то, что N имеет неправильное значение1).
С — перенос. При выполнении операций сложения значение этого разряда устанавливается равным значению переноса из старшего разряда. Когда в соответствии с программой производится сложение n-разрядных чисел без знака, С = 1 указывает на то, что истинный результат больше чем 2П—1. При операциях вычитания значение разряда С определяется наличием или отсутствием засма из старшего разряда. Когда вычитаются числа без знака, С=1 означает, что истинный результат меньше 0.
Установка разрядов условий согласуется с длиной операндов. Так, при операциях над байтами значение разряда N совпадает со значением 7-го разряда результата, а при операциях над словами — со значением 15-го разряда. Однако правила установки разрядов условий для разных команд и процессоров могут различаться. Когда существует вполне определенное соглашение, для всех процессоров используются одни и тс же правила; например, разряд V во всех процессорах устанавливается в 1, если при выполнении команды ADD над числами в дополнительном коде происходит переполнение; в противном случае V устанавливается в 0. Однако во многих случаях правила установки могут быть в какой-то степени произвольными.
Какие значения, например, должны принять разряды условий в результате выполнения операции загрузки? Очевидно, что разряды N и Z должны принять значения в соответствии с величиной, загрузка которой только что была произведена. Если же говорить о разрядах С и V, то в большинстве процессоров V устанавливается в 0, а значение разряда С не изменяется. Причины использования такого правила заключаются в том, что сброс разряда V обеспечивает правильное выполнение определенных условных переходов с учетом знака, в то время как защита разряда С от изменения важна при выполнении операций с повышенной точностью. В таком случае, какими должны быть правила установки для операций записи? Должны ли они совпадать с правилами, используемыми при операциях загрузки? Возможно, что да. Однако некоторые разработчики вычислительных машин считают, что операция запи-
0 То есть значение вне диапазона чисел, которые можно представить.—» Прим, перев..
си вообще не должна влиять на разряды условий. В общем случае для выяснения точных правил установки разрядов условий необходимо самым тщательным образом изучить руководства по применению того или иного процессора.
В процессоре Z8000 для знакового разряда вместо N используется обозначение S; в процессоре 8086 разряды условий называются флажками условий и обозначаются CF, OF, SF и: ZF. У процессора 9900 имеется видоизмененный набор флажков, который обеспечивает получение той же информации. Что- касается процессора MCS-48, то набор его разрядов условий и условных переходов является неполным. В то же время некоторые процессоры имеют дополнительные разряды условий, которые используются при выполнении специальных операций:
Р — четность. Указывает на то, что число разрядов результата, содержащих 1, является четным.
Н — дополнительный (или вспомогательный) перенос. Значение этого разряда совпадает при выполнении сложения со значением переноса между 3-м и 4-м разрядами байта; данный разряд используется при операциях в двоично-десятичном коде. .
D — десятичная коррекция. Разряд D служит для указания того, какая операция была выполнена последней — сложение или вычитание; используется совместно с разрядом Н при операциях в двоично-десятичном коде.
X — расширение. Разряд X является разрядом переноса, который устанавливается так же, как и разряд С, но только не при всех операциях; используется в процессоре 68000 для выполнения арифметических операций с повышенной точностью.
На разряды условий оказывают влияние почти все команды пересылки и обработки данных. Однако иногда возникает необходимость в проверке операнда, который нс требуется куда- либо пересылать или обрабатывать. В этом случае используется команда проверки TEST. По команде TEST dst производится чтение операнда dst и установка разрядов условий в соответствии с его значением; никаких других действий при этом нс выполняется.
Время от времени возникает необходимость в установке в каком-либо разряде заранее заданного значения. Для выполнения этой операции в большинстве процессоров предусмотрены такие команды, как
CLRC
С := 0;
CLRN
N : = 0:
SETC
С:= 1;
SETN
N := 1;
CLRV
V : = 0;
CLRZ
Z : = 0;
SETV
V:- 1;
SETZ
Z : = 1;
Разряды условий являются частью «состояния процессора» — понятие, которое было определено в гл. 5. Эти разряды вместе
с другими разрядами, содержащими информацию о состоянии процессора, часто объединяют в один n-разрядный регистр, называемый регистром слова состояния процессора (PSW). Как показано на рис. 8.1, в процессоре 6809 такой регистр называется регистром кода условий (СС). По команде PSHS СС содержимое всех разрядов условий как единое целое записывается в стек, из которого в случае необходимости их можно извлечь и восстановить с помощью команды PULS СС.
I I— Перенос
IПереполнение
—Нуль
Отрицательный результат-маска прерыванияДополнительный перенос। маска быстрого пре рывания
Полное (общее) состояние стека
Рис. 8.1. Регистр кода условий (СС) процессора 6809.
В последующих разделах будет показано, как можно оказывать воздействие на состояние разрядов условий. Условные переходы, при выполнении которых производится проверка значений указанных разрядов, будут подробно описаны в разд. 8.6.
ПЕРЕСЫЛКА ДАННЫХ
В настоящее время наиболее часто используемыми командами в процессорах являются команды пересылки данных. С помощью таких команд осуществляется пересылка данных из одного места в другое в регистрах и памяти. В процессорах, позволяющих выполнять операции над данными различной длины (байтами, словами и двойными словами), для каждого формата данных предусмотрены отдельные команды пересылки. Код условий при пересылке данных обычно устанавливается в соответствии со значением данных, пересылка которых только что была осуществлена.
Двумя наиболее важными командами пересылки данных являются команды LD (Load — загрузка) и ST (Store — запись). Традиционно мнемоника LD служит для обозначения операций, при которых данные пересылаются из памяти в какой-либо регистр процессора, тогда как мнемоническое обозначение ST означает пересылку из регистра в память. Однако существуют и отклонения от этого правила.
В процессоре Z8000 одно и то же мнемоническое обозначение LD языка ассемблера используется для обоих указанных направлений передачи данных, однако соответствующие им
команды имеют разные коды операций. Ассемблер устанавливает подразумеваемое направление передачи, исходя из поряд- ка следования операндов. В процессорах MCS-48 и 8086 мнемоника MOV также используется для обоих направлений передачи. В этих машинах пересылки типа память-память запрещены.
В PDP-11, 68000 и 9900 для обоих направлений передачи служит мнемоническое обозначение MOV. Это обусловлено тем, что команда MOV является двухадресной командой, для которой источник и приемник одновременно могут быть или регистрами, или ячейками памяти, следовательно, для указанных машин возможны пересылки типа память-память.
С помощью команд пересылки данных последние передаются из источника в приемник. Однако нс для всех ЭВМ существует одна и та же договоренность для языков ассемблера относительно направления передачи. В большинстве ЭВМ используются команды следующего формата:
MOV dst,src dst:=src;
LD dst.src dst:=src;
ST src,dst dst:=src;
В языках ассемблера машин PDP-11, 68000 и 9900 используются команды другого формата, для которого порядок следования операндов является обратным:
MOV src,dst dst:=src;
В большинстве современных процессоров загрузка данных в стек и их извлечение из стека выполняются с помощью одной команды. Для таких процессоров, как PDP-11, 68000 и 6809, стандартными являются автоинкрементный и автодекрементный способы адресации, так что команды загрузки в стек и извлечения из стека представляют собой частные случаи обычных команд пересылки данных ST—(SP) и LD (SP) + соответственно. В процессоре Z8000 загрузка данных в стек и их извлечение из стека обеспечиваются специальными командами. Во всех упомянутых выше процессорах в качестве указателя стека может использоваться любой адресный регистр. Процессор 8086 также имеет специальные команды загрузки в стек и извлечения из стека, но при их выполнении используется только регистр — указатель системного стека.
В процессоре 6809 предусмотрены две команды — PSHS и PULS, позволяющие загружать в системный стек содержимое одного или нескольких регистров процессора и извлекать данные из стека, помещая их в один или несколько регистров процессора. Для выполнения каждой из указанных операций требуется только одна команда. В процессоре 68000 данные опера-
ции производятся с помощью команд STM и LDM соответственно. При этом в качестве указателя стека может использоваться любой адресный регистр.
Существует также целый ряд других специальных команд пересылки данных. В большинстве процессоров имеются команды, обеспечивающие пересылку содержимого регистра слова состояния процессора и других регистров состояния и управления процессором в аккумуляторы, регистры общего назначения или стек, а также выполнение обратных пересылок. У процессора 6809 есть команда передачи TFR, с помощью которой можно получить копию содержимого любого из регистров центрального процессора в каком-либо другом регистре той же длины. В некоторых процессорах предусмотрена команда обмена (ХСН), с помощью которой осуществляется обмен содержимого двух регистров или регистра и ячейки памяти; команда SWAP позволяет производить обмен двух половин операнда (например, обмен байтов операнда, представленного словом одинарной длины). Некоторые процессоры располагают командами LDM (Загрузка групповая) и STM (Запись групповая), позволяющими записывать содержимое группы регистров в память и восстанавливать его из памяти при использовании одной команды. Такие команды очень удобны при обращении к подпрограммам и обработке прерываний. Наконец, существуют команды, с помощью которых можно пересылать блоки данных из одной области памяти в другую.
СЛОЖЕНИЕ И ВЫЧИТАНИЕ
В любой вычислительной машине основной арифметической операцией является двоичное сложение. Типичная команда сложения ADD имеет следующий формат:
ADD dst.src dst := Badd(dst.src.O);
Команда ADD обеспечивает сложение двух n-разрядных операндов при нулевом начальном переносе, запоминание п-раз- рядной суммы в dst и установку разрядов условий. Некоторые примеры установки разрядов условий при сложении 4-разряд- ных чисел приведены в табл. 8.1. Арифметический вес разряда переноса равен +16.
Большинство процессоров может также выполнять операцию вычитания, с помощью которой операнд источника вычитается из операнда приемника с использованием правил вычитания чисел в дополнительном коде:
SUB dst.src dst := Badd(dst,Bcom(src),l);
{= dst—src}
Таблица 8.1. Установка разрядов условий при выполнении сложения
Операция n z v с
СложениеСлож ени е
чиселчисел
ОО знаком без знака_
0100
4- 0010
ОНО0000
+44
+ +2+2
4-66
0101
4- 0111
110010 10
4-55
4- 4-7+
- 4, переполнение12
1101
4- ООН 00000 1 0 1
-313
+ +3+ 3
00, перенос-1
1001
4- ПОР 01010 0 11
-19
+ -4+ 12
4- 5, переполнение5, перенос = 1
Это означает, что сначала производится инвертирование значений отдельных разрядов операнда sre, а затем полученный результат при начальном переносе, равном 1, прибавляется к операнду dst. Однако существует одно очень важное отличие этой операции от эквивалентного сложения. Во всех вычислительных машинах, рассматриваемых в данной книге, за исключением 9900, разряд условия С устанавливается в 1, если выполнение команды SUB приводит к возникновению заема. Как показано в приведенных ниже примерах с использованием 4-разрядных чисел без знака, бит заема представляет собой дополнение бита переноса, возникающего при соответствующей операции сложения:
1—начальный перенос
91001
50101
40100, заема нет
, 1001
+ 1010
11 0100 Перенос =1, следовательно, заем = 0
1—начальный перенос
20010
131101
0010
+ 0010
5—16
0101, заем= 10|0101 Перенос = 0, следова
тельно, заем = 1
1—начальный перенос
_ 7 _ 01110111
70111+ 1000
00000, заема нет 110000 Перенос = 1, следова
тельно, заем = 0
При вычитании 4-разрядных двоичных чисел вес заема равен —16. Более полное представление об установке разрядов условий дает табл. 8.2.
Вычитание является очень удобной операцией для сравнения величин двух чисел. Например, X меньше Y тогда и только тогда, когда X—Y меньше 0. Поэтому если выполнение команды SUB X, Y приводит к получению отрицательного результата, то X меньше Y.
Обычно при сравнении двух чисел запоминать получаемую разность не требуется. Команда сравнения СМР обеспечивает вычитание двух операндов и установку разрядов условий без запоминания разности. Данная команда используется вместе с командами условного перехода так, как это показано в разд. 8.6. Подобно команде SUB, команда СМР устанавливает разряд С в 1, если при вычитании возникает заем, а не перенос. Формат команды СМР в процессорах обычно является следующим:
СМР dst,srcУстановить разряды условий в соответствии
с dst—src
В PDP-11 и 9900, как обычно, используется обратный порядок следования операндов src и dst:
СМРsrc,dstУстановить разряды условий в соответствии
с src—dst
Тем не менее следует отметить, что для этих машин выполнение операции вычитания также производится в обратном порядке, так что почти во всех процессорах, рассматриваемых в данной книге, по команде СМР P,Q выполняется одна и та же операция^ (Р—Q)1).
В процессоре 68000 по команде СМР src,dst производится операция dst—src, порядок вычитания операндов в которой является обратным. Поэтому последовательность команд СМР P,Q; BGT OUT приводит к выполнению перехода, если Q>P. Порядок следования операндов src и dst в машинной команде СМР и операция dst—src в процессоре 68000 определяются аппаратными средствами. Ассемблер процессора 68000 в принципе позволяет решить данную проблему путем изменения порядка выбора src и dst в поле операнда команды СМР, считывая эту команду как СМР dst,src. Однако при использовании стандартного языка ассемблера процессора 68000 решение этой проблемы обычно возлагается на программиста.
Таблица 8.2. Установка разрядов условий при выполнении вычитания
Операция
N
Z
V с
Вычитание чисел со знаком
Вычитание чисел без знака
0100
- 0010
0010
0 0 0 0
+4
- + 2
+ 2
4
- 2
2
0101
- 0111
1110
1
0
0 1
+ 5
- +7
-2
5
- 7
14, заем-1
1101
~ 1101
0000
0
1
0 0
-3
3
0
13’
- 13
0
1001
- 0100
- +4
9
- 4
0101
0
0
1 0
+5, переполнение
5
КОМАНДЫ С ОДНИМ
ОПЕРАНДОМ
Поскольку при изменении значений счетчиков и просмотре таблиц чаще всего используются операции прибавления или вычитания 1, во многих процессорах для этих целей предусмотрены короткие команды:
INC dstDEC dstINC dstDEC dstdst:= dst+1; dst := dst—1;dst:= dst+1; dst := dst—1;
INC dst
DEC dst
INC dst
DEC dst
dst:= dst+1; dst := dst—1;
dst:= dst+1; dst := dst—1;
В указанных командах приемником dst может служить любой из регистров; большое число процессоров позволяет также увеличивать или уменьшать на единицу содержимое ячеек памяти.
В некоторых процессорах команда INC dst по очень простой причине не является полностью эквивалентной команде ADD dst,=H=l- если команда ADD может приводить к изменению значения разряда С, то команда INC нет. Такая же взаимосвязь существует между командами DEC и SUB. Это означает, что условные переходы, при которых производится проверка разряда С, будут выполняться правильно после реализации команд ADD и SUB, а не команд INC и DEC. Исторически эта аномалия возникла в мини-ЭВМ PDP-11/20, управление циклами в
которой обычно осуществлялось следующим образом:
LOOP: ...REPEAT
DEC CNTent := ent—1;
BNE LOOPUNTIL cnt=0;
Проблемы возникали в том случае, когда такие циклы использовались для выполнения умножения и деления, а также арифметических операций с повышенной точностью. В этих циклах желательно передать значение разряда С из конца одного в начало следующего повторения цикла; следовательно, команды INC и DEC предназначались для сохранения значения разряда С. В выпущенной позднее мини-DBM PDP-11/45 используется команда SOB (Вычесть 1 и выполнить переход), которая по существу является комбинацией команд DEC и BNE и позволяет решить указанную проблему, не оказывая никакого влияния на состояние разрядов условий. В процессоре 68000 используется дополнительный разряд условия X, значение которого при операциях с повышенной точностью изменяется точно так же, как и значение разряда С, но остается неизменным при выполнении команд пересылки данных и управления циклами.
Другими командами с одним операндом являются команды CLR (Очистка), СОМ (Получение дополнения до 1) и NEG (Получение противоположного значения), которые на расширенном языке Паскаль описываются следующим образом:
CLRdstdst:=O;
COMdstdst := Bcom(dst);
NEGdstdst: = Badd (O.Bcom(dst),1);
Команда COM позволяет получить инверсные значения отдельных разрядов операнда dst, или, другими словами, образовать дополнение операнда dst до 1. Команда NEG путем образования дополнения до 2 обеспечивает получение значения, противоположного dst — сначала производится инвертирование значений отдельных разрядов, а затем прибавляется 1. Разряды условий при этом устанавливаются в соответствии с результатом операции, однако правила установки разрядов С и V в различных процессорах значительно отличаются друг от друга. Для команды NEG существует твердое правило, которое заключается в том, что разряд С устанавливается в 1, если при подразумеваемом вычитании из 0 возникает заем (т. е. если операнд dst отличен от 0), а разряд V устанавливается в 1 только в том случае, если dst представляет собой «лишнее» отрицательное число 10...0.
УПРАВЛЕНИЕ ВЫПОЛНЕНИЕМ ПРОГРАММЫ
Команды управления выполнением программы имеют очень важное значение, поскольку являются простейшими командами, которые позволяют использовать условные действия и организовывать повторения в программах на машинном языке. Самой простой командой управления выполнением программы является команда безусловного перехода JMP. В большинстве процессоров эта команда позволяет задавать исполнительный адрес с помощью различных способов адресации. В отличие от других команд, при выполнении которых по исполнительному адресу производятся чтение и запись операнда, по команде JMP осуществляется загрузка исполнительного адреса в счетчик команд PC, и управление, таким образом, передастся команде, записанной по этому адресу: .
JMP dst PC := effectiveAddress(dst);
Использование регистровой и непосредственной адресации с командой JMP нс имеет смысла и является запрещенным. Тем нс менее другие способы адресации позволяют сделать команду JMP чрезвычайно гибкой. Например, абсолютный и относительный способы адресации могут применяться для выполнения переходов к определенным ячейкам памяти, в то время как косвенная регистровая адресация и адресация с индексированием дают возможность реализовать операторы типа CASE путем динамического вычисления адреса перехода, используя для этого какой-либо регистр (см. упр. 8.6).
Команда CALL (Вызов или переход к подпрограмме) обеспечивает вызов подпрограмм, машинных эквивалентов процедур языка Паскаль или функций. В большинстве современных процессоров указателем стека служит один регистр, а команда CALL обеспечивает сохранение адреса возврата путем его записи в стек перед переходом к подпрограмме:
CALL dst Push (PC); PC := effectiveAddress(dst);
После выполнения подпрограммы управление с помощью команды возврата RET, обеспечивающей выталкивание содержимого верхнего элемента стека и запись его в PC, передается опять вызвавшей программе:
RET РС:=Рор;
В процессорах, не имеющих стека, по команде CALL адрес возврата помещается в определенный регистр, а возврат может быть выполнен с помощью команды JMP, использующей косвенную регистровую адресацию. Соответствующие примеры подпрограмм будут рассмотрены в гл. 9.
Команды условного перехода предназначены для проверки определенного условия и осуществления перехода, реализуемого только в том случае, если условие выполнено. В процессорах, в которых регистр кода условий не предусмотрен, типичными условиями перехода являются следующие: «Содержимое аккумулятора равно нулю» и «Содержимое аккумулятора отрицательно». В процессорах с регистром кода условий для
/512118 70
I КОПУ слабоеСмещение |
Рис. 8.2. Формат коротких команд условного перехода, выполнения перехода осуществляется проверка значений одного или нескольких разрядов условий.
Команды перехода, в которых адрес перехода задается с использованием адресации относительно счетчика команд, называют также командами ветвления. Как показано в разд. 7.3, типичные команды ветвления содержат короткое смещение для получения адреса перехода недалеко от текущей команды. Это позволяет уменьшить длину команды ветвления и достичь позиционно-независимой программы. Так как команды с короткими (1-байтовыми) смещениями являются очень удобными, их используют в большинстве процессоров; типичный формат таких команд показан на рис. 8.2. Смещение интерпретируется как целое число со знаком в дополнительном коде и в случае выполнения условия перехода прибавляется к содержимому счетчика команд. В процессорах 6809 и 68000 имеются также команды условного перехода с длинными (в 1 слово) смещениями.
В большинстве процессоров с регистром кода условий используется 15 стандартных условий перехода, приведенных в табл. 8.3; пары ВСС— BHS и BCS — BLO представляют собой символические обозначения одних и тех же машинных команд. С помощью команд перехода, приведенных в верхней части таблицы, производится проверка состояния отдельных разрядов условий.
В средней части таблицы даны команды, которые обеспечивают проверку величин целых чисел со знаком. Если гг выражает одно из состояний LT, GE, LE, GT, NE или EQ, то X гг Y только тогда, когда X—Y гг 0. Поскольку выполнение команды CMP X,Y приводит к установке разрядов в соответствии с разностью X—Y, то последовательность команд CMP X,Y и Вгг LABEL вызовет переход только в том случае, если X гг Y.
Таблица 8.3. Типичные команды условного перехода
Тип перехода
Мнемоническое обозначение команд
Условие перехода
Переход по знача-
BRA offset
Выполняется всегда
нию отдельного
BCS offset
Установка разряда
С=1
разряда условия
BCG offset
переноса
Сброс разряда пере-
С=0
BVS offset
носа
Установка разряда
V=1
BVC offset
переполнения
Сброс разряда пере-
V=0
BMI offset
полнения Отрицательный ре-
0X7 TIL'TQ
N=1
BPL offset
оуЛЫ d 1
Положительный ре
N=0
BEQ offset
зультат Равно нулю
Z=1
BNE offset
Не равно нулю
Z=0
Переход с учетом
BLT offset
Меньше нуля
N XOR V=1
знака
BGE offset
Больше или равно U ХУ TTT/S
N XOR V=0
BLE offset
нулю
Меньше или равно
(N XOR V) OR Z=1
BGT offset
нулю Больше нуля
(N XOR V) OR Z = 0
Переход без учета
BLO offset
Меньше
C = 1
знака
BHS offset
Больше или равно
c=o
BLS offset
Меньше или равно
C OR Z = 1
BHI offset
Больше
C OR Z=0
Заметим, что выражение N XOR V (N Исключающее ИЛИ V) дает знак истинного результата, поскольку равенство V=1 возможно только в том случае, если при вычитании происходит переполнение, приводящее к получению неправильного знака. Условные переходы с учетом знака реализуются правильно также после выполнения команд пересылки и проверки. Переход осуществляется, если операнд, к которому только что было произведено обращение, определенным образом соотносится с нулем1). Это объясняет, почему для команд пересылки данных предусматривается сброс разряда V.
^ Сказанное не имеет отношения к процессору Z8000, разработчики которого допустили оплошность. Команды пересылки данных в этом процессоре не влияют на значение разрядов условий, что создает неудобства. Вместо этого предполагается, что после выполнения загрузки при необходимости должна использоваться команда TEST. Однако эта команда не приводит к сбросу разряда V, поэтому может случиться так, что если после ее выполнения не произвести сброс разряда V с помощью команды RESFLGV, то переход не будет выполнен. Наилучший выход из положения состоит в том, чтобы вообще исключить команду TEST VAR, а вместо нее использовать команду CMP VAR, #0.
* Данная программа обеспечивает нормирование последователь- ^ ности из слов. Каждое слово есть цепочка символов, завершающа* яся пробелом. По окончании последовательности слов ставится
* точка. Всякий раз при поступлении символа <!> очередь раонор- * мировывается и осуществляется печать слова. Символ «!> не ле-
* чатается *
PROGRAM WordQueue;
QLEN
EQU
200
CONST qlen = 200;
ENDCHR
EQU
2EH.
endChar = 1.1;
SPACE
EQU
20H
space =1 1; 1
DMPCHR
EQU
21H
dumpChar = 1!1;
*
VAR inChar : char; {Uses accumulator A}
-QUEUE
RMB
QLEN
queue : ARRAY [1..qlen] OF char;
QENDA
EQU
QUEUE+QLEN-1 •
{Define address of last queue item.}
HEAD
RMB
2
head, tail, X,- Y :J address;
TAIL
RMB
2
{X,Y = index registers}
STACK
RMB
20
{Space for return address stack.}
STACKE
EQU
*
*
PROCEDURE Enqueue(A : char);
ENQ
LDX
TAIL
BEGIN'
ADDX
#1
X := tail + 1;.
CMPX
IQENDA
IF X > Addr(queue[qlen]) {Last addr}
BLS
ENQ2
THEN
LDX
IQUEUE
X := Addr(queue[1]); {First addr}
ENQ2
CMPX
HEAD
•IF X <> head THEN
BEQ
• ENQOUT
BEGIN
STA
@TAIL
MEM [tail] := A;
STX
TAIL
tail := X;
ENQOUT
RTS •
END; {Lose chars on overflow.}
*
END; {Enqueue}.
*
FUNCTION Dequeue : char; {Result in A,}
DEQ
LDX
HEAD
BEGIN {... Z-bit = 1 if queue empty.}
CMPX
TAIL .
X := head;
BEQ
DEQOUT
IF X <> tail THEN
LDA
@x
BEGIN A := MEM[X];
ADDX
#1
X := X + 1;
CMPX
IQENDA
IF X > Addr(queue[qlen]) THEN
BLS
DEQ2
LDX •
IQUEUE
X := Addr (queue [1]) ;
DEQ2
STX
HEAD
head := X;
CLRZ
Z ;= 0;
* ,
END;
* "
ELSE Z := 1; {Z-bit - 1 from BEQ}
DEQOUT
RTS
END; {Dequeue}
WORDQ
LDS
#STАСКЕ
BEGIN {Main Program}
LDX
STX
#QUEUE HEAD
head := Add r (queue [ 1]) ;
STX
TAIL
tail := Addr(queue[1]) ;
JSR
READ
.read(A); {Get a char, returned in Alt
WHILE1
СМРА
#ENDCHR
WHILE inChar <> endChar DO
BEQ
ENDW1
BEGIN
IF1
СМРА
#DMPCHR
IF inChar <> dumpChar THEN
BEQ
JSR
ELSE1
ENQ
Enqueue(inChar)
BRA
ENDIF1
ELSE BEGIN
*
ELSE1
JSR
DEQ
WHILE true DO {GOTOs needed.} BEGIN A := Dequeue;
BEQ
L10
IF Z = 1 {queue empty}
СМРА
#SPACE
THEN GOTO 10;
BEQ
LIO
IF A = space THEN GOTO 10;
JSR
WRCHAR
write(A); {Print char in A.l
BRA
ELSE1
END;
L10
JSR
WRITELN
. 10: writein; {Start new.line.}
*
ENDIF1
JSR
READ
END;
read (inChar); {Get next char.}
BRA
WHILE 1
END;
ENDW1
JMP
1000H
{Return to operating system.}
END
WORDQ
END.- {Main Program}
Рис. 8.3. Программа формирования очередей слов, предназначенная для процессора 6809.
Команды перехода, приведенные в нижней части таблицы, используются после операций сравнения целых чисел без знака. Так как с помощью этих команд производится проверка значения разряда С, то их следует применять только после команд СМР; команды пересылки данных обычно не влияют на значение разряда. С. Выполнение команды CMP X,Y приводит к вычитанию Y из X и установке разряда С в 1, если при вычитании возникает заем. Другими словами, когда X и Y интерпретируются как целые числа без знака, установка разряда С в 1 производится только в том случае, если X меньше Y. Принимая во внимание это замечание, в последних четырех строках таблицы приведены команды условных переходов без учета знака, обеспечивающие проверку комбинаций разрядов С и Z.
Команды BNE и BEQ могут использоваться для чисел как со знаком, так и без знака. Действительно, с помощью этих команд производится поразрядное сравнение байтов или слов.
В программе, представленной на рис. 7.7, для сортировки чисел в диапазоне от —99 до +99 использовались команды условного перехода с учетом знака. В программе, приведенной на рис. 8.3, которая получена из программы, представленной на рис. 3.9, проверка установки указателей начала и конца оче-
реди напротив границы буфера выполняется с помощью команд условного перехода без учета знака (BLS). Использование этих команд обусловлено тем, что в начале и конце очереди содержатся абсолютные адреса памяти, которые являются целыми числами без знака. Наиболее часто встречающейся ошибкой программирования является использование в такой ситуации команд условного перехода с учетом знака. В результате, если производится сравнение чисел с противоположными знаками, программа становится непригодной, причем ошибку выявить достаточно трудно (в рассматриваемом примере такое может произойти только в том случае, если буфер очереди включает в себя границу между верхней и нижней половинами адресного пространства памяти, объем каждой из которых составляет 32К).
В программе, представленной на рис. 8.3, показано также соответствие между операторами языка Паскаль и командами языка ассемблера. Кроме того, в ней используется много команд, уже рассмотренных в данной главе. Следует отметить, что в главной программе на языке ассемблера имеется фрагмент, который не поддается описанию на языке Паскаль без использования оператора GOTO. В этой ситуации весьма полезным был бы оператор EXIT языка Паскаль.
Для организации программных циклов часто прибегают к следующей структуре:
LOOP ... REPEAT
DEC CNTent := ent— 1;
BNE LOOPUNTIL cnt=0;
Две последние машинные команды такого цикла объединяются в одну машинную команду DJNZ (Уменьшение и переход, если не нуль), что позволяет сформировать более короткий цикл:
LOOP ...REPEAT
DJNZ CNT,LOOP ent: = ent—1;
*UNTIL cnt=0;
Помимо того что данная команда приводит к укорачиванию цикла и увеличению скорости выполнения программы, она обладает еще одним достоинством — не влияет на код условий, что очень важно при организации циклов, использующих арифметические операции с повышенной точностью, при реализации которых значение разряда С передается из конца одного повторения цикла в начало следующего. Вариантом команды DJNZ, используемым в PDP-11, является команда SOB (Вычитание 1 и переход, если не нуль).
ЛОГИЧЕСКИЕ ОПЕРАЦИИ
Логические операции обеспечивают обработку данных как массива битов, причем каждый бит обрабатывается независимо от других. К логическим операциям можно отнести уже рассмотренную нами операцию СОМ, поскольку ее выполнение приводит к получению дополнений отдельных битов слова. Фактически в таком контексте данную операцию следовало бы назвать операцией инверсии (NOT). Логические операции могут
Рис. 8.4. Поля упакованных данных:
•а — исходные слова данных Р и Q; б — искомый результат Т.
выполняться и над двумя операндами. Это такие операции, как И (AND), ИЛИ (OR) и Исключающее ИЛИ (XOR):
ANDdst.srcdst:=Band(dst.src);
ORdst.srcdst:=Bor(dst.src);
XORdst.srcdst:=Bxor(dst.src);
О результатах выполнения различных логических операций дает представление обобщенная таблица истинности (табл. 8.4). Операции AND, OR и XOR могут выполняться большинством вычислительных машин. PDP-11 вместо операции AND реализует асимметричную функцию AND, для которой используется обозначение BIC (Сброс разряда):
BIC src.dstdst := Band(Bcom(src),dst);
Как показано в таблице истинности, данная операция приводит к сбросу разрядов операнда dst, соответствующих единичным разрядам операнда sre; операция X BIC Y эквивалентна реализации функции (NOT X) AND Y.
Логические операции чаще всего используются для извлечения и объединения полей разных слов данных. Предположим, например, что 4-разрядное поле F1 представлено разрядами 11—8 слова данных Р, а 7-разрядное поле F2 — младшими разрядами слова Q (рис. 8.4). Другие разряды каждого из этих слов содержат неизвестные величины, отличные от 0. Указанные поля можно объединить в одно слово Т с помощью следующей последовательности команд процессора Z80Q0:
LD
R0,P
AND
RO,#OFOOH
Сброс всех разрядов, кроме 11—8
LD
R1,Q
AND
Rl,*007FH
Сброс всех разрядов, кроме 6—0
OR
RO,RI
Объединение слов
ST
R0,T
Используемые здесь шестнадцатеричные константы 0F00H и 007FH называются масками; в полях констант, соответствующих полям, подлежащим извлечению, находятся единицы, а и остальных полях — нули. По команде AND неиспользованные разряды слов Р и Q устанавливаются в 0, так что нужные поля могут быть объеденены с помощью команды OR. В общем случае для выполнения операций над полями упакованных данных необходимо производить сдвиг полей в слове влево или вправо; этот вопрос будет подробно рассмотрен в разд. 8.8.
Таблица 8.4. Таблица истинности для логических операций
X
Y
NOT X
X AND Y
X OR Y
X XOR Y
X BIC Y
0
0
1
0
0
0
0
0
1
1
0
1
1
1
1
0
0
0
1
1
0
1
1
0
1
1
0
0
В большинстве процессоров при выполнении логических операций разряды N и Z устанавливаются в соответствии с получаемым результатом, разряд V сбрасывается, а разряд С сохраняет свое прежнее значение. Иногда проверку поля слова желательно производить, используя маску и операцию AND, которая обеспечивает установку разрядов условий, но не приводит к записи куда-либо получаемого результата. В процессорах PDP-11 и 6809 это выполняется посредством команды проверки разрядов (BIT).
В программах, обеспечивающих упаковку большого числа булевых флажков в слово данных, желательно иметь возможность адресовать отдельные разряды слова. Это позволяют сделать стандартные команды логических операций, при выполнении которых используются маски с одной единицей или одним нулем. Так, с помощью команд
ANDdst,#7FFFH
ORdst,#8000H
XORdst,#8000H
BITdst,#8000H
производятся соответственно сброс, установка, инвертирование и проверка старшего разряда 16-разрядного слова dst. В некоторых процессорах благодаря наличию команд, оперирующих битами, реализация этих функций осуществляется более эффективно:
BCLR dst,bnum BSET dst,bnum BCHG dst,bnum BTST dst,bnumBCLR dst,bnum BSET dst,bnum BCHG dst,bnum BTST dst,bnumdst [bnum] : =0;
BCLR dst,bnum BSET dst,bnum BCHG dst,bnum BTST dst,bnum
BCLR dst,bnum BSET dst,bnum BCHG dst,bnum BTST dst,bnum
dst [bnum] := 1;
dst [bnum] := Bcom (dst [bnum]);
Z := Bcom(dst[bnum]);
Приведенные здесь команды дают возможность производить обработку определенного бита bnum в слове dst. Команды, оперирующие битами, в зависимости от того, что они задают — отдельный бит bnum или регистр, содержащий bnum, классифицируются как статические или динамические. Процессоры Z8000 и 68000 позволяют выполнять команды обоих указанных типов.
ЦИКЛИЧЕСКИЕ, ЛОГИЧЕСКИЕ И АРИФМЕТИЧЕСКИЕ СДВИГИ
Операции циклического, логического и арифметического сдвига производятся над содержимым одного слова данных (байта или длинного слова); сдвигаются биты этого слова на
Bshl (х, с) III—LJII1_| Bshl (г с) I I I I I I I I I
а
Рис. 8.5. Функции сдвига расширенного языка Паскаль: в — Bshl(x : bitarray; с : bit); б — Bshr(x : bitarray; с: bit).
одну или несколько позиций вправо или влево. Логические, арифметические и циклические сдвиги содержимого регистров могут выполняться всеми процессорами; многие из них позволяют производить эти операции и над содержимым ячеек памяти. В расширенном языке Паскаль используются две функции сдвига — Bshl и Bshr (рис. 8.5). При реализации этих функций массив битов х сдвигается на один разряд влево или вправо. При этом в освободившийся разряд поступает величина с, а на место глобальной переменной ВС типа bit помещается теряемый бит. Указанные функции позволяют задавать все циклические, логические и арифметические сдвиги, которые реализуют-
ся процессорами. В данном разделе описано большое число операций логического, арифметического и циклического сдвига^ однако следует отметить, что не все они выполняются отдельными процессорами.
Термин циклический сдвиг обычно используется для таких, операций, при выполнении которых не происходит потери битов. Типичные команды, обеспечивающие реализацию циклического сдвига на один разряд вправо или влево, описываются на расширенном языке Паскаль (в предположении, что операнды являются 16-разрядными) следующим образом:
RL
dst
{циклический сдвиг влево} dst: = Bshl(dst,dst[15]);
RR
dst
{циклический сдвиг вправо} dst := Bshr (dst,dst [0]);
RLC
dst
{циклический сдвиг влево с использованием разряда С}
dst Bshl(dst.C); С:=ВС;
RRC
dst
{циклический сдвиг вправо с использованием
разряда С)
dst: = Bshr(dst,C); С:= ВС;
Выполнение этих операций показано на рис. 8.6. При простых циклических сдвигах влево и вправо каждый бит операнда сдвигается на один разряд, а в освободившийся разряд поступает бит, который выталкивается с другого конца. При выполнении циклического сдвига с использованием разряда переноса в освободившийся разряд загружается содержимое разряда С, а в разряд С поступает бит, выталкиваемый с другого конца. В некоторых процессорах реализация простого циклического сдвига без использования разряда переноса все же приводит к установке разряда С, хотя этот разряд не используется при выполнении самой операции циклического сдвига:
RL dst {циклический сдвиг влево} С := dst[15]; dst: = Bshl(dst,dst[15]);
RR dst {циклический сдвиг вправо} С : = dst [0]; dst := Bshr(dst,dst[O]);
Разряды условий N и Z устанавливаются очевидным образом, а установка разряда V в разных процессорах осуществляется по-разному и не является очевидной.
Циклический сдвиг с повышенной точностью можно программировать, используя разряд С как звено связи. Предположим, что R0—R1 представляют собой пару 16-разрядных регистров процессора Z8000, в которых содержится 32-разрядный операнд, причем старшее слово находится в R0. Выполнить над этим 32-разрядным операндом операцию, эквивалентную
циклическому сдвигу влево с использованием разряда переноса, можно с помощью двух операций циклического сдвига, предназначенных для сдвига 16-разрядных операндов:
RLC R1RLC R0RLC R1RLC R0RI : = Bshl(Rl,C); С := ВС {старое значение
RLC R1
RLC R0
RLC R1
RLC R0
R1 [15]};
RO := Bshl(RO,C); С := ВС {старое значение
R0 [15]};
Циклический сдвиг содержимого регистров R0—R1 без использования разряда переноса выполняется несколько сложнее.
Рис. 8.6. Выполнение операций циклического сдвига:Рис. 8.6. Выполнение операций циклического сдвига:
Рис. 8.6. Выполнение операций циклического сдвига:
Рис. 8.6. Выполнение операций циклического сдвига:
а — сдвиг влево; б — сдвиг вправо; в — сдвиг влево с использованием разряда переноса; г —• сдвиг вправо с использованием разряда переноса.
Логическим сдвигом обычно называют операции сдвига, при которых в освободившемся разряде устанавливается 0. Выталкиваемый бит при таком сдвиге обычно сохраняется в разряде С, а старое значение С теряется. Другие разряды условий в большинстве случаев устанавливаются так же, как и при выполнении циклических сдвигов. Выполнение логических сдвигов влево и вправо на один разряд показано на рис. 8.7, а их описание для случая, когда используются 16-разрядные операнды, приведено ниже:
SLLdstSRLdstSLLdstSRLdst{логический сдвиг влево} С := dst[15]; dst: = Bshl(dst,O);
SLLdst
SRLdst
SLLdst
SRLdst
{логический сдвиг вправо} С := dst [0]; dst := Bshr(dst,O);
Теперь вернемся к рассмотрению задачи выполнения операции циклического сдвига без использования разряда переноса над 32-разрядным операндом, который содержится в регистровой паре R0—R1. В приведенной ниже последовательности команд команда SLL обеспечивает сдвиг содержимого регистра R1 влево и установку младшего разряда в 0; после этого, если при
циклическом сдвиге содержимого регистра R0 влево возникает перенос, младший разряд регистра R1 устанавливается в 1.
SLL
RlRI := Bshl(Rl.O); С := ВС {старое значение
R1 [15]};
RLC
RORO : = Bshl(RO.C); С := ВС {старое значе
ние R0 [15]};
вес
INC
LSB0
LSBOIF С=1 THEN R1 [0] := 1;
Rl{INC не оказывает влияния на С}
(С теперь совпадает со значением младшего разряда нового содержимого RO,R1}
ТеряемыйТеряемыйЦиклические и логические сдвиги очень удобны для выполнения операций над упакованными данными, т. е. над словами данных, в разных полях которых содержатся две или более
Теряемый
Теряемый
Теряемый бит ‘^OW 0 I 1.1
Рис. 8.7. Логические сдвиги влево (а) и вправо (б).
Рис. 8.8. Четыре поля данных:а — упакованный формат; б — распакованный формат.Рис. 8.8. Четыре поля данных:а — упакованный формат; б — распакованный формат.
Рис. 8.8. Четыре поля данных:
а — упакованный формат; б — распакованный формат.
Рис. 8.8. Четыре поля данных:
а — упакованный формат; б — распакованный формат.
независимые величины. Так, на рис. 8.8 показано 16-разрядное слово данных Р, имеющее четыре поля: Fl, F2, F3 и F4. Данный формат очень удобен для использования в программах, которые должны обеспечивать запоминание большого числа записей данных. При упаковке каждой записи в одно слово требуется значительно меньший объем памяти, чем при использовании четырех полных слов распакованного формата (рис. 8.8,6). Тем не менее при обработке какой-либо определенной записи может возникнуть необходимость в том, чтобы получить копии упакованных величин в распакованном формате, обработать их,
* Данная подпрограмма обеспечивает распаковку * четырех величин упакованного слова Р, помещая их
★ в регистры R1э R2, R3, R4.
*
Система
записи;
Vi= содержимое поля Fi
*
UNPACK LD
R0,P
Get packed word.
CLR
RI
Prepare RI to accept V1.
SLL
RO
Put MSB of VI in C.
RLC
RI
Rotate into R1.
SLL
RO ’
Repeat for one more bit.
RLC
CLR
R1
R2
Prepare R2 to accept V2.
SLL
RO
Put MSB of V2 in C.
RLC
R2
Rotate into R2.
SLL
RO
Repeat for two more bits.
RLC SLL RLC CLR
R2
RO
R2
R3
Prepare R3 to accept V3.
SLL
RO
Put MSB of V3 in C.
RLC
R3
Rotate into R3.
SLL
RO
Repeat for one more bit.
RLC
R3
LD
R4,P
Get another copy of packed word.
AND
R4,#01FFHGet rid of V1,V2,V3; leaving V4.
*
RET
Данная программа
Unpacked values are now in R1-R4.' обеспечивает упаковку четырех величин»
*
находящихся в регистрах R1, R2, R3, R4,‘e упакованное
* СЛОВО Р *
PACKCLR
RO
•Prepare RO to receive packed values
~ SRL
R3
Put LSB of V3 into C.
RRC
RO
Shift into RO.
SRL
R3
Repeat for another bit.
RRC
SRL
RO
R2
Put LSB of V2 into C.
RRC
RO
Shift into RO.
SRL
R2
Repeatfdr two more bits.
RRC
RO
SRL
RRC
R2
RO
SRL
R1
Put LSB of V1 into C.
RRC
RO
Shift into RO.
SRL
R1
Repeat for another-bit.
RRC AND
RO
R4,#01FFHMake sure no garbage with V4.
OR
R0,R4
Merge with V1,V2;V3.
ST
R0,P
Store packed word.
RET
Done, return.
Рис. 8.9. Подпрограммы упаковки и распаковки данных для процессора Z8000. *
а затем произвести упаковку новых величин. На рис. 8.9 приведены подпрограммы, предназначенные для процессора Z8000, которые позволяют выполнить это с помощью комбинации различных сдвигов и логических операций.
При арифметических сдвигах операнды обрабатываются как числа со знаком, представленные в дополнительном коде. Выполнение этих сдвигов производится таким образом, что сдвиг
Теряемый битТеряемый битТеряемый гмбит
Теряемый бит
Теряемый бит
TEZZHj 0
б
Рис. 8.10. Арифметические сдвиги влево (а) и вправо (б).
на один разряд влево эквивалентен умножению на 2, а сдвиг на один разряд вправо — делению на 2. Выполнение арифметических сдвигов на один разряд влево и вправо показано на рис. 8.10; ниже приведено их описание для случая, когда используются 16-разрядные операнды:
SLA dst {арифметический сдвиг влево} С: = dst[15]; dst: = Bshl(dst,O);
SRA dst {арифметический сдвиг вправо} С := dst[O]; dst: = Bshr(dst,dst[15]);
Заметим, что команды SLL и SLA являются эквивалентными. Их единственное возможное отличие состоит в установке разрядов условий; установка разряда V для команды SLL не была определена. При выполнении команды SLA разряду V всегда присваивается значение в соответствии с тем, как интерпретируется арифметическое переполнение: если в результате выполнения операции произошло изменение знакового разряда dst, то разряд V устанавливается в 1, а если нет, то в 0.
При выполнении команды SRA арифметического переполнения произойти не может. Тем не менее в некоторых процессорах вместо сброса разряд V после этой команды принимает неизвестное значение. Данная аномалия проявилась впервые в процессоре PDP-11. Другая проблема, связанная с использованием команды SRA, заключается в том, что когда ее операнд отрицательный, усечение производится в «неправильном» направлении, так что деление числа —1 на 2 приводит к получению —1, а не 0.
Арифметические сдвиги с повышенной точностью могут быть получены путем сочетания арифметических сдвигов с одинарной точностью с циклическими или логическими сдвигами и использования разряда С в качестве звена связи. Например, арифме-
тический сдвиг вправо содержимого пары регистровR0—R1 производится с помощью следующей последовательности команд:
SRAROR0:= Bshr(R0,R0[15]); С:=ВС {старое
содержимое R0[0]};
RRCRIRI := Bshr(Rl.C); С:=ВС {старое содер-
•жимoeRl[0]};
Во многих процессорах для выполнения логических, арифметических и циклических сдвигов с повышенной точностью предусмотрены специальные команды, реализуемые аппаратным способом. Многие процессоры имеют также команды, которые обеспечивают сдвиг операнда сразу на несколько разрядов, например:
SLL dst.nbitsFOR i := 1 ТО nbits DO dst := bshl(dst,O);
В зависимости от того, где задается число разрядов nbits, на которое производится сдвиг, — в поле команды или в регистре, определяемом командой, команды сдвига на несколько разрядов классифицируются как статические или динамические. К сожалению, сдвиги на несколько разрядов с одинарной точностью обычно нельзя использовать для эффективной реализации операций сдвига на несколько разрядов с повышенной точностью, которые необходимы, например, для алгоритмов с плавающей точкой. Это обусловлено тем, что между словами имеется только 1-разрядное звено связи (С).
СЛОЖЕНИЕ И ВЫЧИТАНИЕ
С ПОВЫШЕННОЙ ТОЧНОСТЬЮ
При выполнении арифметических операций с повышенной (например, удвоенной) точностью необходимо, чтобы бит переноса (или заема) передавался от младших слов или цифр к старшим. Для облегчения таких операций используются команды ADC (Add with carry — Сложение с переносом) и SBC (Subtract with carry — Вычитание с переносом). Предположим, что мы имеем два 32-разрядных операнда Р и Q типа длинное целое число без знака (long unsigned integer), которые хранятся в словах памяти PH, PL и QH, QL 16-разрядного процессора Z8000. Тогда 32-разрядную сумму SH, SL этих операндов можно вычислить следующим образом:
LD
RO,PL
SL:= PL+QL;
ADD
R0,QL
ST
R0,SL
LD
RO,PH
SH:= PH+QH+G;
ADC
R0,QH
ST
R0,SH
С помощью команды ADC производится сложение двух операндов и, если перед началом реализации команды значение разряда С равно 1, прибавляется 1. Приведенная выше последовательность позволяет понять, почему команды пересылки данных не должны приводить к изменению состояния разряда С; когда мы приступаем к выполнению команды ADC, разряд С все еще имеет значение, полученное, в результате выполнения команды ADD. Приведенная последовательность может также использоваться в том случае, если 32-разрядные операнды интерпретируются как целые числа со знаком. После выполнения последней команды ST разряд N дает знак результата, а разряд V указывает на переполнение, возникающее при операциях над числами в дополнительном коде. Заметим, что разряд Z в любом случае отражает состояние только старшего слова, а не всего 32-разрядного результата.
По команде SBC производится вычитание sre из dst, и, кроме того, если С= 1, вычитается дополнительная единица. Разность двойной длины Р—Q определяется следующим образом:
LD R0,PLSL:=PL—QL;
SUBRO.QL
. STR0,SL
LDRO,PH SH : = PH—QH—C;
SBCR0,QH
STR0,SH
Приведенная последовательность команд может использоваться для вычитания целых длинных чисел как со знаком, так и без знака, причем замечания относительно установки разрядов условий в данном случае являются такими же, как и при сложении.
Иногда требуется выполнять операции над целыми числами со знаком, длина которых неодинакова. В этом случае необходимо произвести расширение более короткого операнда с тем, чтобы его длина совпадала с длиной более длинного операнда. Длину числа, представленного в дополнительном коде, можно увеличить (расширить) до любого числа разрядов путем простого повторения слева от него значения знакового разряда. Для положительных чисел это правило является очевидным; доказательство того, что данное правило распространяется и на отрицательные числа, читателям предлагается выполнить самим в качестве упражнения. Ниже приведены два примера увеличения длины чисел:
-г 5 ю = 01012= 000001012=00000000000001012
—510= 10112=111110112= 11111111111110112
В большинстве процессоров предусмотрена команда SXT (Sign extend — Расширение знака), с помощью которой знак содержимого младшей половины регистра, регистровой пары или слова памяти воспроизводится в старшей половине.
УМНОЖЕНИЕ И ДЕЛЕНИЕ
Большинство процессоров имеет команды умножения чисел со знаком, без знака или те и другие. Умножение обычно выполняется по одному из алгоритмов, рассмотренных в разд. 4.8, так что при умножении двух слов получается произведение в виде слова двойной длины. Переполнения при этом произойти не может, поскольку результат должен быть представлен словом двойной длины.
Типичная команда умножения имеет следующий формат:
MUL Rn,srcRn,Rn+l := Rn*src;
где Rn — регистр, обеспечивающий хранение одного слова, а src — операнд в виде одного слова, которое находится в регистре или в памяти. Поскольку произведение, получаемое при умножении, является словом двойной длины, то для его хранения используются два последовательных регистра, причем старшее слово записывается в Rn, а младшее слово — в Rn+L В процессорах, позволяющих умножать только числа без знака, умножение чисел со знаком может быть реализовано путем проверки знаков операндов, получения числа с противоположным значением для отрицательного операнда, выполнения умножения над числами без знака и изменения знака произведения, если операнды имели разные знаки.
Умножение чисел без знака с повышенной точностью производится путем использования в качестве простейшей операции короткого умножения. Рассмотрим, например, умножение 2-байтовых операндов ХН, XL и YH, YL, которое приводит к получению 4-байтового произведения. Данная операция может быть записана следующим образом:
XY= (XL+28-XH) • (YL+28-YH)
=XL • YL+28 • (XL • YH+YL • ХН) +216 • XH • YH
Этому уравнению соответствует программа умножения, приведенная на рис. 8.11, в которой четыре байта РЗ, Р2, Pl, Р0 представляют собой произведение, а команда MUL является командой умножения чисел без знака. По сравнению с этой программой умножение с повышенной точностью чисел со знаком и без знака, для реализации которого в качестве простейшей операции используется умножение чисел со знаком, выполняется более сложно.
* Умножение ХН, XL на YH, YL и запись произведения в РЗ, Р2, Р1, РО.
* Все переменные являются 1-6айтовыми и хранятся в памяти *
DBLMUL LDA
XL
P1,P0 := XL*YL;
LDB
YL
MUL
{Multiply A by B, leave product in A,B.}
STB
P0
{High-order prod in A, low-order in B.}
STA
P1
LDA
XL
P2,P1 := 0,P1 + XL*YH;
LDB
YH
•mul
ADDB
P1
STB
P1
ADCA
#0
{This operation can’t produce a carry,у
STA
P2
LDA
YL
P3,P2,P1 :- 0,P2,P1 + 0,YL*XH;
LDB
XH
MUL ADDB
P1
STB
P1
ADCA
P2
{This operation can produce a carry.}
STA
P2
LDA
#0
{CLRA no good — it would also clear CJ
ADCA
#0
{Add in carry,}
STA
P3
{Previous operation can’t carry,}
LDA
XH
P3,P2 := P3,P2 + XH*YH;
LDB
YH
MUL
ADDB
P2
STB
P2
ADCA
P3
{Carry is impossible here too.}
STA
P3
RTS
{Done., return.}
Рис. 8.11. Подпрограмма умножения с повышенной точностью чисел без знака для процессора 6809.
В большинстве процессоров предусмотрена также команда деления. Деление может производиться над числами со знаком, без знака или над теми и другими. В соответствии с алгоритмами, рассмотренными в разд. 4.9, реализация команды деления заключается в делении делимого, представленного словом двойной длины, на делитель в виде одного слова и получении частного, которое должно быть представлено одним словом, и остатка. Если при этом окажется, что делитель равен нулю или для представления частного требуется более одного слова, разряд переполнения устанавливается в 1. Деление чисел со знаком обычно выполняется таким образом, что знак остатка сов*
падает со знаком делимого. Типичная команда деления имеет следующий формат:
DIV Rn.srcDIV Rn.srcRn+1 := Rn,Rn+l DIV src;
DIV Rn.src
DIV Rn.src
Rn := Rn,Rn+l MOD src;
где Rn—Rn+1 представляет собой регистровую пару, a src является делителем, состоящим из одного слова, которое находится в каком-либо регистре или в памяти.
Деление чисел со знаком можно выполнить, применяя в качестве простейшей команду деления чисел без знака. Тем не менее очевидного способа деления с повышенной точностью, реализуемого с использованием команды, обеспечивающей более короткое деление, не существует.
ДЕСЯТИЧНАЯ АРИФМЕТИКА
Две двоично-десятичные цифры можно упаковать в 1 байт таким образом, что старшая цифра будет расположена слева; следовательно, десятичное число с любым числом цифр может
74 3 О
Р |Р//| PZ. |
й \ йН I йк |
С н
А=р*а | ^ |\д |
Рис. 8.12. Двоично-десятичная интерпретация суммы двух байтов.
быть представлено в виде последовательности байтов. Несмотря на то что команды, позволяющие выполнять прямое сложение упакованных двоично-десятичных чисел, предусмотрены только у некоторых типов микропроцессоров, большинство из них имеет команду DAA (Decimal adjust — Коррекция десятичная), которая может использоваться для выполнения этих операций вместе с командами двоичного сложения байтов. Предположим, что два числа PH, PL и QH, QL, каждое из которых состоит из двух цифр, находятся в байтах Р и Q. Тогда сложение чисел Р и Q с целью получения суммы А можно выполнить с помощью обычного двоичного сложения (рис. 8.12). При сложении разряд С принимает значение переноса из 7-го разряда, а разряд Н (дополнительный перенос) устанавливается в соответствии с переносом между 3-м и 4-м разрядами. Команда DAA обеспечивает проверку состояний разрядов С и Н и вос-
становление в аккумуляторе А разрешенного упакованного двоично-десятичного числа, так что сложение упакованных двоично-десятичных чисел Р и Q можно выполнить, используя приведенные ниже команды процессора 6809:
LDA Р Загрузка байта Р в А
ADDA Q Прибавление байта Q
DAA Десятичная коррекция байта суммы в А
Коррекция содержимого А с помощью команды DAA с целью получения правильной двоично-десятичной суммы АН — AL производится следующим образом:
Если значение разряда дополнительного переноса Н равно 1, то сумма PL и QL превышает число 15ю; AL является разрешенной двоично-десятичной цифрой, которая отличается от правильной цифры на 6. Данная ошибка корректируется путем прибавления к AL числа 6; коррекцию АН производить не требуется, поскольку перенос уже произошел.
Если значение AL находится в пределах между Юю и 15ю, то AL не является разрешенной двоично-десятичной цифрой. Коррекция этой ошибки производится путем прибавления к AL числа 6, установки Н в 1 и прибавления 1 к АН. Последняя операция выполняется для осуществления переноса, который должен был произойти; если перед прибавлением 1 значение АН было равно 15ю, то данная операция приводит к установке разряда С в 1.
Если значение разряда С теперь равно 1, то сумма РН и QH больше 15ю; АН является разрешенной двоично-десятичной цифрой, которая отличается от правильной цифры на 6. Коррекция этой ошибки производится путем прибавления к АН числа 6.
Если значение АН находится в пределах между Юю и 15ю, то коррекция производится путем прибавления к АН числа 6 и установки разряда С в 1. Последняя операция выполняется для осуществления переноса, который должен был произойти.
р
0100 оно
46
ООП 1001
39
0101
1000
58
1001 0111
97
Q
0101 0010
52
0100 0101
45
0111
1001
79
ОНО 1000
68-
A=P+Q
1001 1000
98
0111 1110
7Е
1101
0001
D1
1111 1111
FF
С, Н
00
00
0
1
00
DAA
1001 1000
98
1000 0100
84
ООП
0111
137
ОНО 0101
165
С, Н
00
01
1
1
1 1
Таблица 8.5. Примеры десятичного сложения, выполняемого с помощью двоичного сложения и команды DAA
Пример 1
Пример 2
Пример 3
Пример .4
Некоторые примеры выполнения команды DAA приведены в табл. 8.5. Заметим, что если сумма двух чисел, каждое из которых состоит из двух цифр, превышает 99ю, то разряд С будет установлен в 1, так что сложение двоично-десятичных чисел с повышенной точностью можно выполнить с помощью команд ADDC. Предположим, что два двоично-десятичных числа Р
*Р есть двоично-десятичное число, состояаее из 6 ци.р
*и хранимое в Р2, Р1 , РО
*Q- другое такое же число, хранимое в Q2, Q1 , ЙО
*Сумма 5 = Р+й хранится в S2, S1 , SO
ADD6D
LDA
P0
ADDA DAA STB LDA ADCA
QO
, SO
P1 -
QI
Add rightmost digits of P and Q.
Adjust. .
Save rightmost digits of S.
Add middle digits, including carry.
Рис.
DAA
STAS1
LDAP2
ADCA Q2 DAA STAS2
RTS
8.13. Подпрограмма
Adjust.
Save middle digits.
Add leftmost digits, including carry.
Adjust.
Save leftmost digits.
Done, C=1 if result exceeded 6 digits
сложения чисел, состоящих из шести цифр, для
процессора 6809.
и Q, каждое из которых состоит из шести цифр, хранятся как 3-байтовые величины Р2, Pl, РО и Q2, QI, Q0. Тогда сумму S2, SI, S0, которая состоит из шести цифр, можно вычислить с помощью подпрограммы, приведенной на рис. 8.13.
Кроме сложения можно также производить вычитание упакованных двоично-десятичных чисел, но правила коррекции будут другими (сформулировать эти правила предлагается читателям в качестве упражнения). В процессоре Z8000 имеется дополнительный разряд условия D, который указывает на то, какая операция была произведена последней — сложение или вычитание, что позволяет выполнять команду DAA в соответствии с правилами, необходимыми для того или иного случая. В процессоре 8086 просто используются две команды типа DAA — одна служит для выполнения шагов коррекции при сложении, а другая при вычитании.
Двоично-десятичные числа со знаком могут быть представлены в прямом коде. Один из способов, который может использоваться для хранения в памяти двоично-десятичных чисел, представленных таким образом, показан на рис. 8.14. Для того чтобы выработать правила сложения и вычитания указанных чи-
\3нак | D5 | D4 j D3 | D2 | D1 | • DO ]а
Рис. 8.14. Представление двоично-десятичного числа со знаком, состоящего из шести цифр:
а — обычное представление; б — размещение в памяти.
* Р есть двоично-десятичное числоа состоящее из 5 цифр и знака#
* представленное в дополнительном коде и храненное в Р2, Р1 , PQ.
* Данная подпрогр амма обеспечивает вычисление дополнения
* этого числа до 10 и сохранение результата в Р2, Р1, Р0
NEG6D
LDA
#99H
P2 := 99H
- P2
; {9s1 complement}
SUBA
P2
STA
P2
LDA
#99H
PI := 99H
- PI
; {9s1 complement)
SUBA
PI
STA
P1
LDA
#99H
A : = 99H -
■ P0;
{9s* complement}
SUBA
P0
ADDA
#1.
P0 := A +
1; {
10's complement}
DAA
{Adjust}
STA
P0
LDA
P1
PI := PI 4
■ C;
{Propagate carries)
ADCA
#0
DAA
{Adjust}
STA
P1
LDA
P2
P2 := P2 4
- C;
{Propagate carries}
ADCA
#0
DAA
{Adjust}
STA
P2
RTS
{Done, result
in P2,P1,PO)
*
{Overflow
if A
= 10H; Why?}
Рис. 8.15. Подпрограмма вычисления дополнения числа Р, состоящего из шести цифр, до 10, предназначенная для процессора 6809.
сел, сначала необходимо произвести проверку знаков операндов. При сложении чисел Р и Q существуют четыре случая (и последовательности команд), которые определяются знаками Р и Q.
Если для представления двоично-десятичных чисел со знаком используется дополнительный код десятичной системы счисления, то эти числа можно непосредственно складывать друг с другом с помощью подпрограммы, приведенной на рис. 8.13. Вычитание может быть выполнено путем образования дополнения вычитаемого и последующего сложения с уменьшаемым. Дополнение до 10 числа, состоящего из шести цифр, которое хранится в байтах Р2, Pl, Р0, можно получить, как показано на рис. 8.15, где знаковой является крайняя цифра слева. Данная цифра имеет вид 0000, если число положительное, и 1001, если оно отрицательное. В программе сначала производится вычисление дополнения до 9 числа Р2, Pl, Р0, а затем с целью получения дополнения до 10 прибавляется 1.
УПРАЖНЕНИЯ
Микропроцессор Z8000 имеет совмещенный разряд условий P/V, который при одних операциях устанавливается' в соответствии е четностью, а при других — в соответствии с переполнением, возникающим при действиях над числами в дополнительном коде. Команда TESTB устанавливает разряд P/V в соответствии с четностью. Почему для подавляющего большинства программ это нежелательно? "
В микропроцессоре Z8000 используется такой же разряд P/V, как и в микропроцессоре Z80 фирмы Zilog, который является улучшенным вариантом микропроцессора 8080 фирмы Intel: В микропроцессоре 8080 имеется разряд Р, но нет разряда V, в результате чего программирование условных переходов с учетом знака является весьма затруднительным. В микропроцессоре Z80 сделана попытка разрешить эту проблему путем использования имеющегося разряда Р для указания переполнений, возникающих при арифметических операциях, отсюда и название, разряда — Р/V. Для совместимости с Z80 в микропроцессоре Z8000 вместо того, чтобы использовать отдельные разряды Р и V, сохранен один разряд P/V, что приводит к затруднениям при использовании команды TESTB. Можете ли вы сказать, как решена проблема с разрядом P/V в микропроцессоре 8086 фирмы Intel, который является преемником микропроцессора 8080? Удачное ли это решение?
Каков арифметический вес разряда С после выполнения сложения 16- разрядных чисел без знака? Что можно сказать по этому поводу после вычитания таких чисел?
По команде CMP src ТО dst микропроцессора 68 000 фирмы Motorola выполняется та же операция, что и по команде SuB src FROM dst. Почему порядок вычитания операндов по этой команде должен быть обратным?
Измените программу сортировки, представленную на рис. 7.7, таким образом, чтобы она позволяла производить сортировку чисел в диапазоне от 1 до 255 и оканчивалась при поступлении на вход 0.
Покажите, как, используя команды перехода с косвенной адресацией, можно осуществить трансляцию на язык ассемблера следующего оператора CASE языка Паскаль:
CASE select OF
statement А\
2,3,8: statementB;
4,7: statementC\
statementD;
statementE;
END;
При «нитевидной* записи программ (threaded code) вместо последовательности следующих друг за другом вызовов подпрограмм используется таблица начальных адресов подпрограмм, например:
* NON-THREADED THREADED
*
Вызовы подпрограмм
«Нить»:
FIRST
JSR
SUBRI FIRST
FCW
SUBRI
JSR
SUBR2
FCW
SUBR2
JSR
SUBR3
FCW
SUBR3
JSR
SUBRI
FCW
SUBRI
JSR
SUBR3
FCW
SUBR3
JSR
SUBR27
FCW
SUBR27
END
JMP
1000H
FCW
1000H
Возврат в операцион
ную систему
♦
Типичный формат подпрограммы:
SUBRI
LDA
VAR SUBRI
LDA
VAR
Первая команда
RTS
JMP
@(X) +
Автоинкрементная
косвенная адресация
♦
Начало:
MAIN
JMP
FIRST MAIN
LDX
#FIRST Начало «нити»
JMP
@(X)+
Объясните, какие действия производятся в соответствии с представленной программой, и выскажите свои соображения относительно скорости выполнения и длины «нитевидной» программы по сравнению с обычной программой.
Оптимизируйте подпрограмму BCNT1S процессора Z8000, приведенную на рис. 5.21, используя для этих целей команду BIT микропроцессора Z8000, обеспечивающую проверку отдельных разрядов.
Перепишите подпрограмму BCNT1S процессора Н6809 таким образом, чтобы подсчет единиц осуществлялся путем выполнения серии из восьми циклических сдвигов и проверок.
Напишите программу на языке ассемблера, предназначенную для инициализации таблицы байтов N1S[O..255], где NlS[i] содержит число единиц в двоичном представлении числа i. Для подсчета числа единиц при каждом входе в таблицу используйте серию из восьми циклических сдвигов и проверок.
Измените программу FNDPRM, представленную на рис. 7.13, таким образом, чтобы обнаружение всех простых чисел между 2 и 8000 по-прежнему производилось только для 1000-байтового массива PRIME и путем упаковке в каждый байт восьми компонентов булева массива.
Напишите подпрограмму на языке ассемблера, предназначенную для вычисления всех простых сомножителей целого числа без знака N. Используйте алгоритм, приведенный ниже.
PROCEDURE Factors (N : integer);
VAR rem,quot, I: integer;
BEGIN
WHILE N> =2 DO
BEGIN
I : = 1
REPEAT {Попытка разделить на целые числа от 2 до N}
I := 1 + 1;
{Следующие три строки обеспечивают деление N на I, в результате которого получается частное quot и остаток rem) quot := 0; rem := N;
WHILE rem> = I DO
BEGIN rem := rem—I; quot := quot+1 END;
{Проверка значений I, пока не найдено то, на которое N делится без остатка}
UNTIL rem=0;
{Работа приведенного выше оператора закончится, когда I=N, наихудший случай}
writein (I); {Вывод простого сомножителя}
N := quot; {Установка N равным последнему полученному частному}
END;
END;
Напишите последовательность команд микропроцессора 6809, которая обеспечивает деление 8-разрядного целого числа со знаком, находящегося в аккумуляторе А, на 2 и усечение как положительных, так и отрицательных чисел таким образом, что при делении 1 на 2 получается 0.
Преобразуйте алгоритм, приведенный на рис. 4.3, 4.6, 4.7 и 4.9, в программу на языке ассемблера для процессоров 6809, Z8000 или любого другого процессора.
Обеспечивает ли следующая последовательность команд процессора 6809 корректное получение" числа с противоположным значением для 2-байтового целого числа ХН—XL, представленного в дополнительном коде? Поясните ответ.
СОМХН
NEGXL
BNESKIP
INCХН
SKIP
Приведенная ниже подпрограмма для процессора 6809, с помощью которой производится сложение двух 3-байтовых целых чисел Р2, Pl, Р0 и Q2, QI, Q0, при некоторых значениях чисел Р и Q оказывается непригодной. Объясните почему и внесите исправления в подпрограмму.
MY3SUMS
LDA
P0
Сложение младших байтов
ADDA
Q0
STA
SO
Сохранение младшего байта суммы
LDA
Pl
Сложение средних байтов
вес
SKIP1
INCA
Прибавление 1 к А, если С=1
SKIP1
ADDA
Qi
STA
SI
Сохранение среднего байта суммы
LDA
P2
Сложение старших байтов
вес
SKIP2
INCA
Прибавление 1 к А, если С=1
SKIP2
ADDA
Q2
STA
S2
Сохранение среднего байта суммы
RTS
Выполнено, возврат
Объясните, почему при возврате из приведенной выше подпрограммы в вызывающую программу значение разрядов С, V и N отражает истинное состояние 24-разрядного результата, а значение разряда Z нет.
Измените подпрограмму, которая используется в предыдущем упражнении, таким образом, чтобы она по-прежнему выполнялась правильно и в то же время чтобы при возврате из нее значение разряда Z отражало истинное состояние 24-разрядного результата. (Значения разрядов С, V и N по- прежнему должны быть правильными.)
Напишите подпрограмму для процессора 6809, обеспечивающую получение числа с противоположным значением для целого 24-разрядного числа Р в дополнительном коде и запись результата обратно в Р. Переменная Р должна храниться в трех последовательных байтах памяти.
Две приведенные ниже последовательности команд служат для вычитания целых чисел без знака и проверки получаемого результата. Равнозначны эти последовательности или нет?
LDA UNS1 LDA UNS2
SUBA UNS2 NEGA
BHS LABEL ADDA UNS1
BHS LABEL
Перепишите представленную на рис. 5.6 программу умножения для процессора Н6809 таким образом, чтобы она приводила к получению 2-байтового результата PRODH, PRODL. Для этого можно использовать любые команды процессора 6809, за исключением команды MUL, но новая программа также должна реализовать умножение путем многократного повторения операций сложения.
Перепишите представленную на рис. 5.19 программу умножения для процессора Н8000 таким образом, чтобы она приводила к получению 2-байтового результата в регистрах RH2—RL2. Для этого можно использовать любые команды процессора Z8000, за исключением команды MULT, но новая программа по-прежнему должна реализовать умножение путем многократного повторения операции сложения. *
Составьте в соответствии с упр. 5.13 такой алгоритм преобразования двоичного кода в код ASCII, в котором для получения десятичных цифр используется деление на 10 (см. также разд. 4.3). Используйте этот алгоритм для написания программы на языке ассемблера для процессора Z8000 (или любого другого процессора), обеспечивающей преобразование двоичного кода в код ASCII с использованием команды деления.
Выполните упр. 8.12 для машины, которая имеет команду деления, а не реализует эту операцию путем многократного повторения операции вычитания.
Сформулируйте правило обнаружения переполнения, возникающего при сложении упакованных двоично-десятичных чисел, представленных в дополнительном коде десятичной системы счисления.
ЛИТЕРАТУРА
Данные о типах операций, выполняемых вычислительными машинами, пока не систематизированы; большинство инженеров по вычислительной технике и разработчиков ЭВМ знакомятся с различными типами операций, исходя из собственного опыта работы с различными ЭВМ. В настоящей главе сделана попытка обобщить и систематизировать часть этой информации.
Значительное число работ было посвящено созданию небольших эффективных наборов команд ЭВМ. В статье Элисона [Allison D., A Design Philosophy for Microcomputer Arhitectures, Computer, 10, 2 (Febr. 1977)] рассматривается вопрос о построении машины, ориентированной на язык высокого уровня, или, другими словами, вопрос приспосабливания машинных команд и архитектуры ЭВМ к языку высокого уровня, на котором будут составляться программы. Разработчики микропроцессоров руководствуются также работами, в которых изучается вопрос о том, как часто используются те или иные команды (например, Shustek L. J., Analysis and Performance of Computer Instruction Sets, Ph. D. thesis, Stanford University, 1978).
Определение всех возможных случаев установки разрядов условий является в значительной мере искусством, чем наукой. В большинстве микропроцессоров использование разрядов условий основано на опыте работы с мини-ЭВМ PDP-11, однако не все проблемы разрешены и для этой машины [Russell R., The PDP-11: A Case Study of How Not to Design Condition Codes, Proc. 5th Annual Symp. on Computer Architecture, IEEE Publ. No. 78CH1284-9C (April 1978)]. В руководстве по применению 16-разрядного микропроцессора MC68000, выпущенном фирмой Motorola (см. приложение А), можно познакомиться с соображениями, которые учитывались его разработчиками при выборе необходимых разрядов условий.
ПОДПРОГРАММЫ И ПАРАМЕТРЫ
Подпрограммы в значительной мере определяют структуру программ на любом языке программирования независимо от его уровня. Подпрограмма — это последовательность команд, которые определены и хранятся только в одном месте программы, однако их можно вызвать для выполнения из одной или нескольких точек программы. Примером часто используемых подпрограмм в типичной ЭВМ являются последовательности команд, обеспечивающие запись символа на оконечное устройство (терминал) или считывание символа с этого устройства.
Следующее преимущество использования подпрограмм является очевидным: объем программы уменьшается за счет хранения часто используемой последовательности команд в одном месте. Вместо того чтобы повторять всю последовательность команд каждый раз, когда в этом возникает необходимость, в программу включают одну команду или короткую последовательность команд для вызова требуемой подпрограммы. Другое преимущество использования подпрограмм особенно существенно при разработке больших по объему программ: частные задачи могут быть определены и обработаны подпрограммами, для которых четко заданы взаимосвязи с остальной частью программы. В этом случае разные программисты могут разрабатывать различные подпрограммы, кроме того, можно составлять, отлаживать, оптимизировать и модифицировать отдельные подпрограммы более или менее независимо от остальной части программы. Действительно, разработка большой программы была бы невозможна без подпрограмм, которые позволяют выполнить декомпозицию программы, т. е. свести решение общей задачи к решению ряда небольших по объему задач.
Все преимущества подпрограмм наиболее ярко проявляются при использовании параметров. Параметр — это фиктивная переменная в определении подпрограммы. Параметр обеспечивает выделение места в памяти, причем идентификация параметра с действительной переменной или константой осуществляется всякий раз при вызове подпрограммы. Фиктивная переменная в определении подпрограммы называется формальным параметром, в то время как переменная или величина, используе-
мая при конкретном вызове, называется фактическим параметром. Поскольку при вызовах подпрограммы могут использоваться различные фактические параметры, одна и та же подпрограмма может служить для выполнения идентичных вычислений со многими наборами различных данных. Например, подпрограмма PrintAvg (х,у) предназначена для вывода на печать средней величины двух формальных параметров х и у. Вызов подпрограммы PrintAvg с тремя различными наборами фактических параметров завершится выводом на печать трех различных результатов: PrintAvg(l,5) обусловит появление числа 3; PrintAvg (17,100) приведет к результату 58.5, а PrintAvg (26,58) вызовет печать числа 42.
В гл. 2 мы представили программы на языке Паскаль с подпрограммами, называемыми процедурами и функциями. До сих пор мы использовали процедуры и функции языка Паскаль, не вдаваясь глубоко в их сущность. Теперь мы остановимся на этом вопросе более подробно, что позволит получить более полное представление об этом языке и о соответствии его процедур и функций подпрограммам на языке ассемблера. Для рассмотрения двух указанных проблем имеются следующие причины:
знакомство со структурой процедур и функций языка Паскаль поможет улучшить структуру соответствующих подпрограмм на языке ассемблера;
понимание программистом процедур и функций языка Паскаль принесет определенную пользу в документировании программ на языке ассемблера;
знание соглашений по передаче параметров на языке ассемблера поможет объяснить «загадочные» явления, происходящие при выполнении программ, представленных на языке высокого уровня: почему, например, значения локальных переменных в процедуре на языке Паскаль становятся неопределенными каждый раз, когда осуществляется выход из данной процедуры? (см. разд. 9.3.6); •
ясное понимание соглашений по передаче параметров как на языке высокого уровня, так и на языке ассемблера крайне необходимо, когда программист осуществляет увязку программных модулей, представленных на языке высокого уровня и на языке ассемблера, для выполнения какой-либо задачи.
ПРОЦЕДУРЫ И ФУНКЦИИ
ЯЗЫКА ПАСКАЛЬ
Процедуры
Процедурой на языке Паскаль является программно определенная последовательность операторов, которая может быть вызвана одним оператором, называемым процедурным опера-
тором. Процедура определяется в разделе описания процедуры и функции программного блока (рис. 9.1). Блок в описании процедуры аналогично программному блоку включает описания и операторную часть. Идентификатор определяет имя процедуры, в то время как список параметров обеспечивает присвоение имен и типов формальным параметрам. Процедура может вызываться с помощью процедурного оператора, в котором
Рис. 9.1. Синтаксическая диаграмма раздела описания процедуры и функции.
используется имя данной процедуры и перечислены любые фактические параметры, заменяющие формальные параметры (рис. 9.2). Процедура (или функция) должна быть определена до ее вызова).
Описания в процедурном блоке определяют константы, переменные, типы, а также дополнительные процедуры и функции, которые являются локальными по отношению к текущей процедуре. На такие локальные элементы нельзя ссылаться вне области действия текущей процедуры. Если за пределами процедуры существуют элементы с теми же именами, что и внутри процедуры, то такие элементы переопределяются в пределах процедуры без изменения их внешних определений. Если в определении процедуры указывается элемент, который не определен в процедуре, то элемент должен быть определен за ее пределами.
На рис. 9.3 представлено несколько примеров применения
правил, определяющих области действия процедур. Глобальные переменные common и maxi используются в пределах процедуры РгосА. Однако глобальная переменная maxi переопределена в пределах действия процедуры РгосВ. Переменная temp, кото-
Рис. 9.2. Синтаксическая диаграмма процедурного оператора.
PROGRAM ScopeRules (input,output);
VAR common, maxi : integer;
PROCEDURE ProcA;
VAR temp, x : integer;
BEGIN
x := maxi;{uses global ’maxi’}
common := temp + x; {global 1 common1, local •temp1, ’x’} END;
PROCEDURE ProcB;
VAR maxi, mini : integer; {'maxi1 redefined locally}
BEGIN
mini := maxi; {OK - ’mini1 and ’maxi' both local) temp := 0; {error - ’temp' undefined in current scope)
END;
BEGIN
read(common,maxi); {OK - initialize globals)
temp := 10; {error - ’temp1 undefined in current scope)
END.
Рис. 9.3. Примеры правил, определяющих области действия процедур.
рая является локальной по отношению к процедуре РгосА, ошибочно используется в процедуре РгосВ и в главной программу. Хотя правила определения области действия процедур допускают многократное использование одного и того же идентификатора в процедуре, руководствуясь соображениями четкости и корректности, лучше использовать разные имена идентификаторов в различных процедурах.
Операторная часть процедурного блока указывает действия, которые выполняются каждый раз при вызове процедуры. Зна-
чения всех локальных переменных не определяются, когда производится вход в операторную часть, кроме того, они не сохраняются между последовательными вызовами процедуры.
PROGRAM RemSpace (input,output);
{Remove spaces from input text terminated by a period.}
VAR inChar : char;
BEGIN
' REPEAT
REPEAT read(inChar) UNTIL inChar <> 1 1;
write(inChar);
UNTIL inChar = 1. ’; END.
Рис. 9.4. Программа на языке Паскаль, обеспечивающая удаление пробелов во входном тексте.
На рис. 9.4 представлена программа, которая исключает пробелы из входного текста. Модифицированный вариант программы приведен на рис. 9.5. Здесь использована процедура SkipSpaces, заменяющая оператор REPEAT самого глубокого уровня вложения. Строка SkipSpaces в теле программы являет-
PROGRAM RemSpaceProc (input,output) ;'
{Remove spaces from input text terminated by. a period.}
VAR;inChar : char; .
PROCEDURE SkipSpaces;
BEGIN
REPEAT read (inChar) UNTIL inChar O -.1 1 ;
END;
BEGIN
REPEAT
SkipSpaces;
write (inChar) ;
UNTIL inChar = 1 . ';
END.
Рис. 9.5. Программа удаления пробелов с помощью процедуры.
ся процедурным оператором. Когда встречается этот оператор» то выполняется последовательность команд, определенная программистом (т. е. оператор REPEAT). При использовании указанной процедуры в данном примере программа в действительности становится длиннее. Однако в общем случае программы» в которых используются процедуры, обладают рядом достоинств:
удачно выбранное имя процедуры улучшает восприятие программы благодаря четкому описанию операции, подлежащей выполнению;
представление программы в виде иерархической структуры процедур с четко определенными взаимосвязями и взаимо-
PROGRAM Compress (input,output);
{Compress a series of spaces in input text terminated by a period.} VAR inChar : char;
PROCEDURE SkipSpaces;
{A series of 1 to 26 spaces is translated into ’#’ followed by a character 1 A1 to 1 Z1. Longer series are truncated to 26 spaces.} VAR sent : integer;
BEGIN
sent := -1;
REPEAT
read(inChar); sent := sent + 1;
UNTIL inChar <> 1 1;
IF sent > 26 THEN sent := 26;
IF sent > 0 THEN write(111,chr(prd(1A1)-1+scnt)) ;
END;
BEGIN
Skipspaces;
WHILE inChar <> 1.1 DO
BEGIN
write (inChar);
SkipSpaces;
END;
END
Рис. 9.6. Программа сжатия цепочек пробелов.
действиями облегчает разработку, откладку, обслуживание и модификацию программы;
если производится многократный вызов процедуры, то объем программы сокращается по сравнению с программой, в которой тело процедуры повторяется всякий раз, когда в этом возникает .необходимость.
На рис. 9.6 представлена более сложная программа обработки пробелов. Вместо исключения пробелов цепочки пробелов преобразуются в символ #, за которым следует буква, соответствующая числу пробелов в цепочке. Кроме того, в главной программе вместо оператора REPEAT используется оператор WHILE; по этой причине точка в конце не печатается. Этот пример свидетельствует о следующем: 1) процедура не имеет параметров, но связана с главной программой посредством глобальной переменной inChar; 2) процедура располагает одной локальной переменной sent, значение которой повторно устанав*
ливается при каждом вызове; 3) вызов процедуры производится из двух различных точек главной программы, причем первая точка предшествует началу цикла WHILE; 4) процедура может быть модифицирована для лучшего выполнения своей функции без изменения главной программы.
Функции
Функция является последовательностью операторов, определенных программистом, которая присваивает значение имени функции (т. е. подставляет значение). Функция определяется аналогично процедуре (см. рис. 9.1). Заголовок функции подо-
PROGRAM RemSpaceFunc (input, output) ;
(Remove spaces from input text terminated by a period.}
VAR inChar : char;
FUNCTION NextNonspace : char;
VAR tempChar : char;
BEGIN
REPEAT read (tempChar) «UNTIL tempChar <> ’ ’; NextNonspace := tempChar;
END;
BEGIN .
' REPEAT
inChar := NextNonspace;
write(inChar) ;
UNTIL inChar -=• 1 .1;
END.
Рис. 9.7. Программа удаления пробелов с помощью функции.
бен заголовку процедуры, но в нем, кроме того, должен быть определен тип значения, возвращаемого функцией. Имя функции (идентификатор) должно присваивать значение указанного типа в пределах функционального блока. Подобно процедурному блоку, функциональный блок может иметь собственные локальные константы, типы, переменные и вспомогательные процедуры и функции.
Основное различие между процедурами и функциями состоит в способе их вызова. В каком бы месте программы ни появились имя функции и список фактических параметров в выражении вызывающей программы, выполняется операторная часть функции, а последнее значение, присвоенное имени функции в операторной части функции, возвращается в выражение. ■ На рис. 9.7 представлена программа, в которой используется функция чтения очередного символа, отличного от пробела, находящегося во входном тексте. Переменная inChar необходи-
ма потому, что функция вызывается всякий раз, когда ее имя появляется в выражении. Следующая главная программа, хотя она синтаксически вполне корректна, осуществляет чтение двух символов, отличных от пробела, за одну итерацию, но производит запись только одного из них:
BEGIN
REPEAT write(NextNonspace) UNTIL NextNonspace='.'; END.
Кроме того, для программы необходимы локальная переменная tempChar и оператор присваивания, содержащиеся в операторной части функции программы, представленной на рис. 9.7. В другом варианте записи операторной части функции в операции сравнения не используется значение, только что присвоенное величине NextNonspace:
BEGIN
REPEAT read (NextNonspace) UNTIL NextNonspace <>'.'; END;
Вместо этого производится рекурсивный вызов самой функции. Если рекурсивные вызовы не требуются, имя функции не должно присутствовать в выражениях операторной части функции.
ПАРАМЕТРЫ В ПРОГРАММАХ,
ПРЕДСТАВЛЕННЫХ НА ЯЗЫКАХ
ВЫСОКОГО УРОВНЯ
Параметры в программах на языке Паскаль
Как процедуры, так и функции языка Паскаль обрабатывают параметры аналогичным образом. Описание процедуры или; функции указывает формальные параметры в списке параметров, как показано в синтаксической диаграмме на рис. 9.8. Если параметры отсутствуют, то список параметров пуст, как это- было в примерах, приведенных в предыдущем разделе. Если же- имеются один или несколько параметров, то в каждый формальный параметр помещается список с указанием типа, как показано ниже:
PROCEDURE PrintAvg (х : real; у: real);
PROCEDURE FindChar (target: char; terminator : char; max : integer);
FUNCTION Xor (p : boolean; q : boolean) : boolean;
FUNCTION Power (x : real; n : integer) : real;
FUNCTION Prime (num : integer) : boolean;
Если два или более формальных параметров одного и того же типа указаны последовательно, то список может быть сокращен:
PROCEDURE PrintAvg (x,y:real);
PROCEDURE FindChar (target, terminator : char; max : integer);
FUNCTION Xor (p,q : boolean) : boolean;
Формальные параметры могут указываться в любом пор яд* ке, а параметры одного типа не обязательно группировать в одном месте. Однако, когда осуществляется вызов процедуры или функции, фактические параметры должны заключаться в
Рис. 9.8. Синтаксическая диаграмма списка параметров.
круглые скобки и их последовательность должна быть той же, что и в определении:
VAR
a,b : real; i,j,len : integer; f 1 ,f2,f3 : boolean;
BEGIN
PrintAvg (a,b);
FindChar ('t', V,len); f3 := Xor(fl,f2);
a := Power (a*b,i+j);
fl := Prime(j+1);
Значащие параметры
В языке Паскаль используются четыре типа параметров (табл. 9.1). Приведенные выше примеры содержат только значащие параметры. Когда указывается значащий параметр, фактический параметр в вызове параметра может быть любым выражением, результат вычисления которого принадлежит к тому
Таблица 9.1. Типы параметров, используемые в языке Паскаль
Тип формального параметра
Синтаксис
Требуемый фактический параметр
Действие назначения в процедуре
Значащий
Идентификатор: тип
Выражение
Локальное
Переменный
VAR идентификатор: тип
Переменный
Измененная переменная
Процедурный
PROCEDURE идентификатор
Имя процедуры
Не разрешено
Функциональный
FUNCTION идентификатор: тип
Имя функции
Не разрешено
же типу, что и формальный параметр. Константа или перемен* ная указанного типа является простейшим примером подобного- выражения. Когда осуществляется вызов процедуры, то про* изводится вычисление выражения, а результирующее значение
' PROGRAM FactorialsAndSquares (input,output); VAR n : integer;*
FUNCTION Fact (i : integer).: real;
VAR prod : real;
BEGIN
prod := 1;
WHILE i>1 DO,
BEGIN prod:=prod*i; i:=i-1 END; •
Fact := prod;-
END;
BEGIN read (n).;.
WHILE n>0 DO {Pass a copy of n to Fact} BEGIN writein (n, Fact (n) ,n*n) ; read (n) END; END.
Рис. 9.9. Программа вычисления факториала и квадрата чисел.
копируется в область параметров и передается в процедуру. Ко* пирование выполняется даже и в том случае, когда результат представляет собой большую структурную композицию, как, на* пример, массив. Процедура может модифицировать формальный параметр с помощью операторов присваивания, но это относится только к копии, хранимой в области параметров. Например, программа, приведенная на рис. 9.9, вычисляет значения фак* ториала и п2 для каждого числа п. Когда определяется величина Fact, то число п не сбрасывается даже в том случае, если копия числа п в области параметров уменьшается на единицу^
*9.2.3. Переменные параметры
Значащие параметры являются удобным способом передачи входных параметров для процедуры или функции. Однако очень часто возникает необходимость для процедуры передавать выходные результаты программе, вызвавшей процедуру, используя один или несколько параметров. Примером подобной ■ситуации является процедура обмена значений, представленная -на рис. 9.10. В процедуре Swap объявляются переменные параметры х и у. Когда переменный параметр указывается в проце-
PROGRAM Swapping (input,output);
VAR a,b : integer;.
PROCEDURE Swap (VAR x,y : integer);
VAR t : integer; BEGIN'
t := x; x := y; У := t; '■
END; BEGIN a := 1; b := 2;
Swap(a,b); {Pass the addresses of a,b to Swap} , write (a,b);■
END-
*Phc. 9.10. Программа, использующая процедуру обмена значений.
дуре или функции языка Паскаль, то фактический параметр, используемый в вызовах, должен быть переменной того же типа. Адрес переменной, а не копия этой величины передается процедуре, а все операторы в процедуре манипулируют непосредственно данной переменной. Таким образом, программа на рис. 9.10 действительно осуществляет обмен переменных значений величин а и b и печатает цифры 2 1. Если бы в определении процедуры использовались значащие параметры, то программа выводила бы печать цифры 1 2.
Параметры процедуры и функции
Язык Паскаль также разрешает передачу имен процедур и функций в качестве параметров. На рис. 9.11 приведена программа, в которой одна процедура («использующая») воспринимает имя другой («передаваемой») процедуры как параметр. Процедуры и функции редко служат в качестве параметров в языке Паскаль, но когда они используются подобным образом, необходимо придерживаться следующих правил:
если «передаваемая» процедура имеет параметры, то они должны быть значащими;
передается только имя процедуры или функции, но не их параметры; следовательно, параметры должны быть сформированы «использующей» процедурой;
PROGRAM ArrayProcs (input,output) ;
CONST len = 80;
TYPE eNum = 1..len;
VAR charBuffer : ARRAY [1..len] OF char;
PROCEDURE DoBuff (PROCEDURE proc); {'using-procedure1}
VAR i : integer;
BEGIN
FOR i := 1 TO len DO proc(i);
END;
PROCEDURE Init (i : integer); {'passed-procedure’}
BEGIN
charBuffer[i] := 1 ';
END;
PROCEDURE Reade (i : integer); {'passedrprocedure'} BEGIN
read(charBuffer[i]) ;
END;
PROCEDURE Printc (i : integer); {'passed-procedure'}
BEGIN
write(charBuffer [i]);
END;
BEGIN
DoBuff (Init) ;
DoBuff(Printc) ; {Printa blank line.}
writein;
DoBuff(Reade); {Read and print a line.)
DoBuff(Printc);
END.
Рис. 9.11. Программа с именами процедур, передаваемыми как параметры.
компилятор не обязательно должен проверять, что количество параметров, требуемых для «передаваемой» процедуры, равно количеству параметров, указанных в определении «использующей» процедуры.
Параметры в программах на других языках
высокого уровня
Соглашения по передаче параметров, принятые в других языках высокого уровня, могут совпадать с подобными соглашениями в языке Паскаль или отличаться от них. Например,
в Алголе предусмотрено два способа передачи параметров. При использовании способа вызова по значению в языке Алгол параметры обрабатываются аналогично тому, как в языке Паскаль производится обработка значащих параметров. Способ вызова по имени в языке Алгол не имеет аналога в языке Паскаль. Определено, что этот способ оказывает такое же действие, как текстуальная замена фактическими параметрами соответствующих формальных параметров в подпрограмме. Такой способ известен как правило замены. Хотя указанный способ позволяет писать процедуры, подобные процедуре Swap, таким же образом, как в языке Паскаль, осуществляется использование переменных параметров, однако между этими методами существует определенная разница. Рассмотрим, например, действие правила замены на тело процедуры Swap, если next представляет собой массив целых чисел и осуществляется вызов процедуры Swap (i.next [i]):
BEGIN
t := i;
= next[i];
next[i]: = t;
END;
Предполагаемое действие вызова состоит в том, чтобы выполнить свопинг величин i и next[i]. Предположим, что i = 5, next[5] =4, a next[4] =0. Тогда вызов процедуры Swap (i.next [i]) устанавливает i=4 (правильно) и next[4]=5 (ошибочно, так как мы хотели установить next [5] =5).
Способ вызова по значению в языке Фортран аналогичен вызовам по значению в языках Паскаль и Алгол, за исключением того, что значение (возможно, модифицированное) в области параметров копируется обратно в первоначальный фактический параметр, когда подпрограмма завершает свою работу^ В языке ПЛ/1 предусмотрен способ, называемый вызовом па ссылке, который аналогичен использованию в языке Паскаль переменного параметра и отличается тем, что позволяет употреблять выражение в качестве фактического параметра, временно выделяя переменную область памяти для значения выражения.
Прежде чем приступить к составлению какой-либо процедуры, которая присваивает значения параметрам, программист должен досконально изучить соглашения по передаче параметров конкретного языка высокого уровня. Следует отметить, что переменные и значащие параметры языка Паскаль точно соответствуют параметрам, которые широко используются в подпрограммах языка ассемблера. Значащие параметры соответствуют числам, символам или другим величинам, передаваемым в под-
программу, в то время как переменные параметры соответству- ют указателям или адресам. В следующем разделе мы обсудим
вопрос о передаче параметров в подпрограммы на языке ассемблера.
ПОДПРОГРАММЫ И ПАРАМЕТРЫ
ЯЗЫКА АССЕМБЛЕРА
Методы вызова подпрограммы
Для выполнения подпрограмм процессор должен иметь особые средства для программы, обеспечивающие сохранение адреса возврата, когда производятся вызов и выполнение под-
* В соответствии с соглашением адрес воз врат а подпрограммы раз * мешается в первых двух байтах подпрограммы. Первая выполняе- * мая команда находится в третьем байте подпрограммы
MAIN
Main program.
LDX
#RET1
Save return address.
STX
SUBR
JMP
SUBR+2
Jump to subroutine.
RET1
• . .
Return here when subroutine finishes.
' )
LDX
#RET2
Save return address again.
STX
SUBR
JMP
SUBR+2
Jump to subroutine.
RET2
. . .
Return here when subroutine finishes.
SUBR
RMB
2
Save two bytes for return address.
STA
P1
.First executable instruction.
LDX
SUBR
Get return address from loc. SUBR.
JMP
ex
Jump to address*rontained in X.
Рис. 9.12. Вызов подпрограмм в ЭВМ 6809 без использования команд JSR и RTS.
программы, а также переход подпрограммы по адресу возвра- та по ее завершении. Казалось бы, адреса возврата подпрограмм могли обрабатываться обычными командами, как это иллюстрирует рис. 9.12 для ЭВМ 6809. В данном случае программист установил соглашение по сохранению адреса возврата для подпрограмм при вызове их программами — выделить в начале подпрограммы два зарезервированных байта для хранения адреса возврата.
Поскольку подпрограммы используются очень часто, все современные процессоры имеют специальные встроенные команды
для вызова подпрограмм и возврата из них. Как показано в разд. 5.2.10, стек (память магазинного типа) является естественным типом памяти для хранения адресов возврата из подпрограмм. Большинство процессоров имеет особый регистр (SP), указывающий стек, в котором располагаются адреса возврата. Команды вызова подпрограммы (JSR или CALL) записывают адреса возврата в стек, а команды выхода из подпрограммы (RTS или RET) осуществляют выборку адресов возврата из стека.
В процессорах, не располагающих аппаратным указателем стека (например, 9900), предусмотрены команды вызова подпрограммы, которые сохраняют адрес возврата в заданном регистре процессора. Когда используются вложенные вызовы подпрограмм, то только от программиста зависит, где хранить содержимое этого регистра — в специальных ячейках памяти или в стеке.
Параметры подпрограммы
В предыдущем разделе мы обсудили несколько типов параметров, используемых в процедурах и 'функциях языка Паскаль. В табл. 9.2 приведены соответствующие типы параметров в подпрограммах на языке ассемблера.
Таблица 9.2. Параметры программ, составленных на языке Паскаль
и языке ассемблера
Тип параметра языка ПаскальТил параметра языка ассемблера
ЗначащийЗначение элемента данных
ПеременныйАдрес переменной
Процедурный илифункциональныйАдрес процедуры или функции
Параметры, указываемые в определениях процедур и функций языка Паскаль, называют входными параметрами, поскольку они передаются от вызывающей программы в подпрограмму. Однако подпрограммы могут также передавать результаты в вызывающую программу. Например, функция языка Паскаль возвращает одно значение в вызывающую программу, в то время как подпрограмма языка ассемблера может передавать вызывающей программе несколько значений, называемых выходными параметрами или результатами.
Чтобы увидеть разницу между переменными параметрами и значащими параметрами, сравните свопинговые процедуры, представленные в программах на рис. 9.13 и 9.14. Главная программа, приведенная на рис. 9.13, передает подпрограмме адреса двух переменных, причем подпрограмма организует доступ
Подпрограммы и параметры345
к переменным, используя косвенный способ адресации. Главная программа на рис. 9.14 передает копии переменных подпрограмме, которые последняя непосредственно обрабатывает. Подпрограмма не имеет доступа к первоначальным переменным.
*
* SWAP
Swap two 8-bit variables x and у whose addresses are passed in index registers X and Y.
LDA@XPut x and у into A and B,
LDB@Y
STA@YSave A and В into у and x.
STB@X
* * MAIN
RTS
Main program — swap the values of VARP and VARQ.
LDX#VARPAddress of VARP.
LDY#VARQAddress of VARQ.
JSRSWAP
Values of VARP and VARQ are now swapped.
VARP
VARQ
RMB1Reserve storage for VARP and VARQ.
RMB1
Рис. 9.13. Подпрограмма обмена, использующая переменные параметры.
*
SWAP
Swap two 8-bit values passed in registers AandB.
STATEMPSave value of A.
TFRB,ATransfer В to A.
LDBTEMPPut saved value ofA into B.
RTS
TEMP * *
MAIN
RMB1Localvariable.
Main program — swap copies of the values of VARP and VARQ
LDAVARPValueofVARP.
LDBVARQValueofVARQ.
JSRSWAP
Copies of VARP and VARQ in A and В are
*
swapped, original VARP and VARQ untouched.
VARP
VARQ
RMB1Reserve storage for VARP and VARQ.
RMB1
Рис. 9.14. Подпрограмма обмена, использующая значащие параметры.
Команда Load Effective Address (LEA) является очень полезной во многих процессорах для передачи переменных параметров. Предположим, что программист хочет произвести свопинг величин VARP и SCORE [J], где SCORE [1...100J
представляет собой массив байтов. Тогда операторы главной программы (рис. 9.13) выполнят следующие действия:
LDX #VARP X является адресом VARP LDY JВвод индекса элемента J массива
LEAYSCORE—1(Y) В Y записан адрес SCORE [J]
JSR SWAP
В этом примере по команде LEAY в Y загружается адрес SCORE [J], вычисленный во время прогона и равный сумме величины J и исполнительного базового адреса SCORE—1.
Из этих примеров следует, что все параметры в программах на языке ассемблера являются действительно «величинами». Значение, которое передается при использовании переменных параметров, является адресом переменной. В данном случае классификация параметров не имеет значения, поскольку подпрограмма и вызывающая программа обеспечивают согласованное использование параметров.
Передача параметров в регистры и ячейки памяти
Для программы наиболее простым путем передачи параметров в подпрограмму является размещение их в регистрах процессора. Аналогично подпрограмма может возвращать результаты в вызывающую программу. Этот способ применялся, например, в подпрограммах предыдущего раздела. Конечно, программист должен быть уверен, что вызывающая программа и подпрограмма согласованно используют регистры для хранения каждого параметра. Распределение регистров для параметров обычно указывается в комментарии в начале подпрограммы. Например, в комментарии подпрограмм WCNT1S (рис. 5.14) отмечено, что входной параметр передается с помощью регистра X, а результат помещается в регистр А.
Если процессор не располагает достаточным количеством регистров, необходимых для хранения всех входных и выходных параметров подпрограммы, то для этой цели могут выделяться определенные ячейки памяти. Эти ячейки памяти связывают непосредственно с подпрограммой, а не с вызывающей программой; при этом любая вызывающая программа помещает входные и выходные данные в заранее определенные для них ячейки памяти. Например, в подпрограмме DIVIDE ЭВМ. 6809 (рис. 6.7) учитывается, что вызывающая программа поместит входные параметры в ячейки памяти DVND и DVSR, а выходные параметры — в ячейки QUOT и REM.
Области параметров
Можно связывать область параметров с вызывающей программой, а не с подпрограммой. В этом случае вызывающая программа помещает параметры в область параметров и пере-
дает подпрограмме базовый адрес области параметров. На рис. 9.15 представлена версия подпрограммы DIVIDE, в которой вызывающая программа использует регистр X для передачи базового адреса области параметров. На рис. 9.16 приведена программа, осуществляющая вызов этой подпрограммы DIVIDE.
* Входные и выходные параметры передаются в S-байтовую об-
* ласть параметров. В регистр X для подпрограммы передается
* базовый адрес области параметров. Указанные ниже смещения
•* определяют размещение каждого параметра в области параметров
*
DVND
EQU
0
2-byte dividend.
DVSR
EQU
2
1-byte divisor.
QUOT
EQU
3
1-byte quotient.
REM
EQU
4
1-byte remainder.
*
DIVIDE
LDA
#8
Initialize count.
STA
LDA
CNT
DVND (X)
Put dividend in A,B.
LDB
CMPA
DVND+1 (X) DVSR (X)
Will quotient fit in 1 byte?
BLO
DIVLUP
Branch if it will.
JMP
SYSOVF
Else report overflow to oper. sys.
DIVLUP
AS LB
Left shift A,В with LSB:=0.
ROLA
BCS
QUOT1
A carry here from MSB means high DVND definitely > DVSR,
CMPA
DVSR(X)
Compare high DVND with DVSR.
BLO
QUOTOK
Quotient bit = 0 if lower.
<QUOT1
INCB
Else set quotient bit to 1.
SUBA
DVSR(X)
And update high DVND.
quotok
DEC
CNT
'Decrement iteration £bunt.
BGT
DIVLUP
Continue until done.
STA
REM(X)
Store remainder.
STB
QUOT(X)
Store quotient.
SYSOVF
RTS
EQU
1800H
Return.
System overflow report address.
CNT
RMB
1
Local storage for counter.
Рис. 9.15. Подпрограмма DIVIDE ЭВМ 6809, использующая область параметров.
Обратите внимание, что область параметров связана с вызывающей программой, а не с подпрограммой. Вызывающая программа может использовать ту же самую область параметров и для других подпрограмм. Если главная программа вызывает много различных подпрограмм, используя единственную область параметров, то при этом обеспечивается уменьшение объема памяти по сравнению с назначением отдельных областей параметров для каждой подпрограммы. Конечно, область
параметров должна быть достаточной для хранения наибольшего количества параметров, используемых в любой одной подпрограмме.
Удобно использовать области параметров для подпрограмм, которые хранятся в постоянных запоминающих устройствах (ПЗУ). Поскольку значения параметров в ПЗУ хранить нель-
Compute PDIVQ := Р DIV Q; PMODQ := Р MOD Q;
*
where all are 1-byte variables in memory.
DIVPQ
LDX
#PARMS
X points to parameter area.
CLR
DVND (X)
Clear high-order dividend.
LDA •
P
Store low-order dividend.
STA
DVND+1 (X)
LDA
Q"
Store divisor.
STA
DVSR(X)
JSR
DIVIDE
Do the division.
LDA
QUOT(X)
Save the quotient*
STA
PDIVQ
LDA
REM(X)
’Save the remainder.
STA
PMODQ
Р
RMB
1
Storage for P, Q, PDIVQ, PMODQ
Q
RMB
1
(all 1-byte variables).
PDIVQ
RMB
1
PMODQ
RMB
1
PARMS
RMB
5
Storage for parameter area.
Рис. 9.16. Программа, вызывающая подпрограмму DIVIDE.
зя, рекомендуется выделять память для них и для вызывающей программы в запоминающем устройстве, обеспечивающем выполнение операций чтение/запись.
Существует особая форма области параметров, называемая линейной областью параметров. В этом случае параметры хранятся в вызывающей программе непосредственно за командой вызова подпрограммы (JSR). Адрес возврата, записываемый командой JSR, в действительности является адресом первого параметра. Перед возвратом в главную программу подпрограмма должна поместить значение адреса возврата за областью параметров; считается, что подпрограмма точно знает количество байтов, которые необходимо пропустить. Линейные области параметров необходимо использовать только в тех случаях, когда фактические параметры для любого вызова подпрограммы являются константами. Если же фактические параметры — переменные, то при вызове подпрограммы необходим» всякий раз модифицировать линейную область параметров, что невозможно при размещении области параметров в ПЗУ и не-
желательно при их размещении в запоминающем устройстве,, обеспечивающем выполнение операций чтение/запись (в некоторых системах управления памятью используется защита по записи программных областей).
Статическое и динамическое распределения памяти
Методы распределения памяти для параметров подпрограммы можно классифицировать как статические и динамические. При статическом распределении ячейки памяти резервируются* для параметров конкретной подпрограммы или вызывающей программы; ячейками оперируют только при выполнении этих программ. В обеих версиях подпрограммы DIVIDE, описанных выше, использовалось статическое распределение памяти. При динамическом распределении параметры хранятся в заданной области памяти во время выполнения подпрограммы, но в остальное время эта область памяти предоставляется для других программ. Передача параметров с использованием регистров (рис. 5.14, 5.21) является самой простой формой динамического! распределения памяти.
Соглашения по передаче параметров с использованием стека
Размещение параметров в стеке магазинного типа является формой -динамического распределения памяти, используемой как лицами, программирующими на языке ассемблера, так № разработчиками компиляторов блочно-структурных языков высокого уровня типа Алгол и Паскаль. Для параметров может использоваться тот же стек, что и для адресов возврата, поскольку большинство процессоров располагает командами для загрузки произвольных данных в стек адресов возврата и извлечения их оттуда.
На рис. 9.17 представлен пример типичного использования стека адресов возврата для передачи параметров. Вызывающая программа резервирует память в стеке для любых выходных параметров, а затем помещает в него входные параметры. На рис. 9.17, а показано состояние стека после вызова подпрограммы, адресный регистр FP в данном случае используется в качестве указателя кадра стека. Подпрограмма сохраняет прежнее- значение указателя кадра стека FP путем передачи этого значения в стек, а затем копирует значение указателя стека (SP) в регистр FP. Указатель кадра стека обеспечивает фиксированную ссылку для доступа к параметрам, которая не изменяется с изменением значения указателя стека (SP), сколько бы эле-
ментов ни было передано в стек. Область стека, доступная во время выполнения подпрограммы, называется кадром стека.
Как показано на рис. 9.17,6, локальные переменные могут также загружаться в стек во время выполнения подпрограммы, доступ к ним может осуществляться путем указания смещения от значения указателя кадра стека (FP). Если вызыва-
Кадрстека 2Кадрстека 2Кадрстека 1Кадрстека 1SPSP- Адрес_ ЗозВр. 1 Входнж парам. 1 BblXO(lH_ парам. 1.- Адрес_ ЗозВр. 1 Входнж парам. 1 BblXO(lH_ парам. 1.SPSPSPSP* Локальн^ перемен. 1* Локальн^ перемен. 1- родные парам."^_- родные парам."^_SPSPЛокальн "перем ^_Локальн "перем ^_Прежнее значИРгПрежнее значИРгАдрес_ВозВр. 2Входные_парап.г_Адрес_ВозВр. 2Входные_парап.г_Кадр (fp-^ стека О 1Кадр (fp-^ стека О 1ааFP^ прежнее. знач. FP 1 Адрес возВр. 1 Входные_ парам. ; Выходи_ парам. 1_FP^ прежнее. знач. FP 1 Адрес возВр. 1 Входные_ парам. ; Выходи_ парам. 1_выходн_ парам, г локальн_ перем. 1 рр -» Прежнее^знач. ГР 1Адрес_ВозВр. 1Входные^ парам. 1выходн_ парам, г локальн_ перем. 1 рр -» Прежнее^знач. ГР 1Адрес_ВозВр. 1Входные^ парам. 1Выходе^ паромуВыходе^ паромувыходн_ парам. 2выходн_ парам. 2SPSPЛокален лерем. 1 Прежнее. FP ЗНОЧГР1Локален лерем. 1 Прежнее. FP ЗНОЧГР1_Адрес_ВозВр. 1_Адрес_ВозВр. 1ббввВходные, парам. 1 Выходк_ парам.1_Входные, парам. 1 Выходк_ парам.1_б.б.Выходя_ ларам. 2Выходя_ ларам. 2Локальн_ перем. 1• Прежнее ЗначГР!_Адрес_ ВозВр, 1 Входные. парам-1 выходи_ др ларам. 1Локальн_ перем. 1• Прежнее ЗначГР!_Адрес_ ВозВр, 1 Входные. парам-1 выходи_ др ларам. 1FPFP99ее- Выходн_ парам.1_- Выходн_ парам.1_
Кадрстека 2
Кадрстека 2
Кадрстека 1
Кадрстека 1
SP
SP
- Адрес_ ЗозВр. 1 Входнж парам. 1 BblXO(lH_ парам. 1.
- Адрес_ ЗозВр. 1 Входнж парам. 1 BblXO(lH_ парам. 1.
SP
SP
SP
SP
* Локальн^ перемен. 1
* Локальн^ перемен. 1
- родные парам."^_
- родные парам."^_
SP
SP
Локальн "перем ^_
Локальн "перем ^_
Прежнее значИРг
Прежнее значИРг
Адрес_ВозВр. 2Входные_парап.г_
Адрес_ВозВр. 2Входные_парап.г_
Кадр (fp-^ стека О 1
Кадр (fp-^ стека О 1
а
а
FP^ прежнее. знач. FP 1 Адрес возВр. 1 Входные_ парам. ; Выходи_ парам. 1_
FP^ прежнее. знач. FP 1 Адрес возВр. 1 Входные_ парам. ; Выходи_ парам. 1_
выходн_ парам, г локальн_ перем. 1 рр -» Прежнее^
знач. ГР 1
Адрес_
ВозВр. 1
Входные^ парам. 1
выходн_ парам, г локальн_ перем. 1 рр -» Прежнее^
знач. ГР 1
Адрес_
ВозВр. 1
Входные^ парам. 1
Выходе^ парому
Выходе^ парому
выходн_ парам. 2
выходн_ парам. 2
SP
SP
Локален лерем. 1 Прежнее. FP ЗНОЧГР1
Локален лерем. 1 Прежнее. FP ЗНОЧГР1
_Адрес_
ВозВр. 1
_Адрес_
ВозВр. 1
б
б
в
в
Входные, парам. 1 Выходк_ парам.1_
Входные, парам. 1 Выходк_ парам.1_
б.
б.
Выходя_ ларам. 2
Выходя_ ларам. 2
Локальн_ перем. 1
• Прежнее ЗначГР!
_Адрес_ ВозВр, 1 Входные. парам-1 выходи_ др ларам. 1
Локальн_ перем. 1
• Прежнее ЗначГР!
_Адрес_ ВозВр, 1 Входные. парам-1 выходи_ др ларам. 1
FP
FP
9
9
е
е
- Выходн_ парам.1_
- Выходн_ парам.1_
Рис. 9.17. Стек с адресами возврата, параметрами и локальными переменными:
^ — непосредственно после вызова подпрограммы SUBR1; б —во время выполнения подпрограммы SUBR1; в — непосредственно перед вызовом подпрограммы SUBR2; а —Ю время выполнения подпрограммы SUBR2; д — непосредственно после возврата из подпрограммы SUBR2; е — после возврата из подпрограммы SUBR1.
ется подпрограмма второго уровня вложения, то параметры вновь передаются в стек и формируется новый кадр стека (рис. 9.17, в и г). Когда выполняется возврат из подпрограммы, то производится очистка стека посредством:
удаления ее локальных данных путем установления значения указателя стека (SP) равным значению указателя кадра стека (FP);
восстановления прежнего значения указателя кадра стека (FP) путем извлечения его значения из стека;
удаления входных параметров из стека и перехода по адресу возврата; при этом в стеке остаются только выходные параметры.
Соглашение о вызове подпрограммы, использующей стек, иллюстрируется подпрограммой DIVIDE (рис. 9.18). Регистр X используется в качестве указателя кадра стека. Программа, ко-
торая вызывает подпрограмму DIVIDE, представлена на рис» 9.19. Состояние стека перед вызовом подпрограммы DIVIDE показано на рис. 9.20, а. Вызывающая программа резервирует
* The offsets below define positions of parameters- and local
* variables in the stack relative to a frame pointer (reg- X)
CNT
EQU
-1
OLDFP
EQU
0
Old value of frame pointer.
RETADR
EQU
2
Return address.
DVSR
EQU
4
1-byte divisor (input).
DVND
EQU
5
2-byte dividend (input).
REM
EQU
7
1-byte remainder (output).
QUOT
EQU
8
1-byte quotient (output).
*
DIVIDE
PSHS
X
Push old frame pointer onto otack.
TFR
S,X
Copy SP into X for new frame pointer
LDA
#8
Push initial count.
PSHS
A^
LDA
DVND (X)
Put dividend in A,B.
LDB
DVND+1 (X)
CMPA
DVSR (X)
Will quotient fit in 1 byte?
BLO
DIVLUP
Branch if it will.
JMP
SYSOVF
Else report overflow to oper. sys,.
DIVLUP
ASLB
Left shift A,В with LSB:=0.
ROLA
A carry here from MSB means
BCS
QUOT1
high DVND definitely > DVSR..
CMPA
DVSR(X)
Compare high DVND with DVSR.
BLO
QUOTOK
Quotient bit = 0 if lower.
QUOT1
INCB
Else set quotient bit to 1.
SUBA
DVSR(X)
And update high DVND.
QUOTOK
DEC
CNT (X)
Decrement iteration count.
BGT
DIVLUP
Continue until done.
STA
REM(X)
Store remainder.
STB '
QUOT (X)
Store quotient.
TFR
X,S
Remove local variables.
PULS
X
Restore frame pointer.
PULS
Y
Get return address, save in Y.
ADDS
#3
Remove input parms (add 3 to SP).
JMP
@Y
Return to address contained in Y.
SYSOVF
EQU
1800H
System overflow report address.
Рис. 9.18. Подпрограмма DIVIDE, передающая параметры в стек.
2 байт в стеке для выходных параметров НЕМ и QUOT, а затем загружает входные параметры DVND и DVSR (рис. 9.20, б) и вызывает подпрограмму DIVIDE. Подпрограмма DIVIDE помещает прежнее значение указателя кадра и счетчика итераций' CNT в стек (рис. 9.20,в). После возврата происходит очистка стека с сохранением только выходных параметров НЕМ и- QUOT (рис. 9.20,г).
Можно заметить, что использование регистра X в качестве указателя кадра (рис. 9.18) является в некотором смысле излишним, поскольку значение указателя стека не меняется в течение всего времени выполнения подпрограммы; можно было бы использовать указатель стека вместо регистра X путем до-
О *0О *0ADDS#-2Make room for’ output parms(SP:=SP-LDAPPush low-order dividend.PSHSACLR .APush high-order dividend =0.PSHSALDAQPush divisor.PSHSAJSRDIVIDEDo the division.PULSAPop remainder and store.STAPMODQPULSAPop quotient and store.STAPDIVQRMB.1Storage for P, Q, PDIVQ, PMODQRMB1(all 1-byte variables)RMB1RMB1ADDS#-2Make room for’ output parms(SP:=SP-LDAPPush low-order dividend.PSHSACLR .APush high-order dividend =0.PSHSALDAQPush divisor.PSHSAJSRDIVIDEDo the division.PULSAPop remainder and store.STAPMODQPULSAPop quotient and store.STAPDIVQRMB.1Storage for P, Q, PDIVQ, PMODQRMB1(all 1-byte variables)RMB1RMB11-11-1byte variables in memorybyte variables in memoryDIVPQDIVPQ’-2)’-2)PDIVQPMODQPDIVQPMODQCompute PDIVQ := where all areCompute PDIVQ := where all areP DIV Q; PMODQ :=P MOD Q;P DIV Q; PMODQ :=P MOD Q;Рис. 9.19. Программа, которая вызывает подпрограмму DIVIDE, использующую стек.Рис. 9.19. Программа, которая вызывает подпрограмму DIVIDE, использующую стек.
О *0
О *0
ADDS
#-2
Make room for’ output parms
(SP:=SP-
LDA
P
Push low-order dividend.
PSHS
A
CLR .
A
Push high-order dividend =
0.
PSHS
A
LDA
Q
Push divisor.
PSHS
A
JSR
DIVIDE
Do the division.
PULS
A
Pop remainder and store.
STA
PMODQ
PULS
A
Pop quotient and store.
STA
PDIVQ
RMB.
1
Storage for P, Q, PDIVQ, PMODQ
RMB
1
(all 1-byte variables)
RMB
1
RMB
1
ADDS
#-2
Make room for’ output parms
(SP:=SP-
LDA
P
Push low-order dividend.
PSHS
A
CLR .
A
Push high-order dividend =
0.
PSHS
A
LDA
Q
Push divisor.
PSHS
A
JSR
DIVIDE
Do the division.
PULS
A
Pop remainder and store.
STA
PMODQ
PULS
A
Pop quotient and store.
STA
PDIVQ
RMB.
1
Storage for P, Q, PDIVQ, PMODQ
RMB
1
(all 1-byte variables)
RMB
1
RMB
1
1-1
1-1
byte variables in memory
byte variables in memory
DIVPQ
DIVPQ
’-2)
’-2)
PDIVQ
PMODQ
PDIVQ
PMODQ
Compute PDIVQ := where all are
Compute PDIVQ := where all are
P DIV Q; PMODQ :=P MOD Q;
P DIV Q; PMODQ :=P MOD Q;
Рис. 9.19. Программа, которая вызывает подпрограмму DIVIDE, использующую стек.
Рис. 9.19. Программа, которая вызывает подпрограмму DIVIDE, использующую стек.
бавления 1 ко всем смещениям. Однако в обобщенной подпрограмме промежуточные результаты вычисления выражения и других расчетов могли бы временно помещаться в стек и извлекаться из него, так что смещения параметров от указателя стека изменялись бы постоянно. В этом случае фиксированный указатель кадра (X) использовать значительно проще, чем указатель стека.
Подпрограммы DIVIDE, приведенные на рис. 9.15 и 9.18, •осуществляют доступ к параметрам в «области параметров», используя адресацию на базе (гл. 7), при которой смещения определяются с помощью адреса, содержащегося в регистре X. Основное отличие между этими подпрограммами состоит в том, что область параметров для подпрограммы на рис. 9.15 выделяется статически во время трансляции программы, тогда как для подпрограммы на рис. 9.18 эта область создается динамически в стеке всякий раз при вызове подпрограммы. При статическом выделении областей параметров требуется одна область для каждой подпрограммы; при наличии стека необходим только достаточный объем памяти для максимального количе-
ства параметров, которые в данный момент «активны», если применяются вложенные подпрограммы. В программе, содер- жаиЦей значительное количество подпрограмм, соглашение по Использованию стека позволяет достичь значительной экономии памяти.
Процедуры и функции языка Паскаль передают параметры в соответствии с соглашением по использованию стека аналогично тому, как это было описано выше. Локальные переменные
SP^iSP^iSP-*.SP-*.DVSRBVNDDVSRBVNDГР-*ГР-*ааббREMQU ОТREMQU ОТSP-указатель стека гр-указатель кадра стека CNT-счетчик итерацийSP-указатель стека гр-указатель кадра стека CNT-счетчик итераций6DVSR- делитель DVND-делимое REM-остаток6DVSR- делитель DVND-делимое REM-остатокгQUQT-частное Old FP-прежнее значение FP^гQUQT-частное Old FP-прежнее значение FP^
SP^i
SP^i
SP-*.
SP-*.
DVSR
BVND
DVSR
BVND
ГР-*
ГР-*
а
а
б
б
REM
QU ОТ
REM
QU ОТ
SP-указатель стека гр-указатель кадра стека CNT-счетчик итераций
SP-указатель стека гр-указатель кадра стека CNT-счетчик итераций
6
DVSR- делитель DVND-делимое REM-остаток
6
DVSR- делитель DVND-делимое REM-остаток
г
QUQT-частное Old FP-прежнее значение FP^
г
QUQT-частное Old FP-прежнее значение FP^
Рис. 9.20. Состояние стека во время выполнения программы DIVPQ:
а —во время пуска; б— перед вызовом подпрограммы DIVIDE; в —после выполнения первых четырех команд подпрограммы DIVIDE; г — после возврата нз подпрограммы.
также хранятся в стеке. Это объясняет, почему значения локальных переменных не сохраняются между последовательными вызовами одной и той же процедуры — указатель стека может показывать разные позиции (ячейки) при каждом вызове, так что переменные могут храниться в различных ячейках памяти при выполнении разных вызовов.
Подпрограммы обработки очередей •
Обсуждение данной темы мы завершим приведением набора подпрограмм ЭВМ 6809, которые обрабатывают очереди, включая комментарии, объясняющие работу этих подпрограмм. Программа, которая использует эти подпрограммы, будет приведена ниже (разд. 11.6.3). Мы придерживаемся мнения, что программы должны содержать всю информацию о себе (рис.
* QUEUE MODULE *
* Этот модуль содержит три подпрограммы для обработки очере-
* дей из байтов (8 бит) Очередь определяется таблицей описания
* очереди и блоком памяти
*Блок памяти очереди
* QDTBL QHEAD (слово) I > I (байт)
*|1||
*IQTAIL (слово)III
*|1||о о о
*QSTRT (слово) II
QEND (слово) I> I (байт)
* Смешения в *
таблице описания очереди
QHEAD
EQU
0
QTAIL
EQU
2
QSTRT
EQU
4
QEND
EQU
6
* В этой таблице последние два слова являются константами, на- * чальная установка которых выполняется во время загрузки, они * представляют начальный и конечный адреса блока памяти (бу*е- *ра), резервируемого для очереди. Первые два слова зарезервиро- * ваны для хранения начала и конца очереди (абсолютные адреса * памяти) и обрабатываются подпрограммами
*
* Если в программе определяется несколько очередей, то для к аж— * дой очереди выделяется отдельная таблица описания очереди и * блок памяти. Например, приведенные ниже псевдокоманды опре- * деляют очередь Q1 (5 байт) и очередь Q2 (100 байт):
*Q1BLK
RMB
5
Storage block
for Q1.
*Q1END
EQU
*-1
Last location
in Qi storage block.
*Q1DT
RMB
4
Ql descriptor
table —
QHEAD and QTAIL,
*
FCW
Q1BLK,Q1END
QSTRT and QEND.
*Q2BLK
RMB
100
Storage block
for Q2.
*Q2END
EQU
*-1
Last location
in Q2 storage block.
*Q2DT
RMB
4
Q2 descriptor
table —
QHEAD and QTAIL
*
FCW
Q2BLK,02END
QSTRT and QEND.
* Подпрограммы
i служат для
инициализации
очереди
(GINIT), до-
* бавления байта в очередь (QENQ) и исключения его из очереди *(QDEQ). В каждую программу для очереди должен быть передан
* адрес таблицы описания очереди
Рис. 9.21. Подпрограммы обработки очередей в ЭВМ 6809.
* SUBROUTINE QINIT - Initialize a queue to be empty.
* INPUTS
* #QDTBL — The address of the queue descriptor table for the
*queue to be initialized, passed in register X.
* OUTPUTS, GLOBAL DATA, LOCAL DATA — None
* FUNCTIONS
•*.1) Initialize the queue to empty by setting QHEAD and QTAIL
*in QDTBL equal to the first address in the queue buffer.
■* REGISTERS AFFECTED — Y, CC
* TYPICAL CALLING SEQUENCE
*LDX
*JSR
#QWT QINIT
«QINIT LDY
QSTRT (X)
Load buffer starting address into Ye
STY
QHEAD(X)
Store into QHEAD and QTAIL.
STY
QTAIL (X)
RTS *
Done, return.
* SUBROUTINE
QENQ — Enqueue
one byte into a queue.
* INPUTS
■* #QDTBL — The address of the queue descriptor table, for the
*queue to be manipulated, passed in register X.
"* QDATA — The byte to be enqueued, passed in register A.
■* OUTPUTS“
* QFULL —- 1 if the 'queue is already full, else 0;
*• passed in condition bit Z.
■* GLOBAL DATA, LOCAL DATA — None.
* FUNCTIONS’
*(1) If the queue described by QDTBL is full, set QFULL to 1.
"*(2) If the queue described by QDTBL is not full, enqueue QDATA,
*and set QFULL to 0.
* REGISTERS AFFECTED — Y, CC
* TYPICAL CALLING SEQUENCE
-*
LDX
#Q1DT
Enqueue byte ABYTE.
LDA
ABYTE
JSR
QENQ
BEQ
OVFL
Branch if queue is full.
QENQ
LDY
QTAIL (X)
Get queue tail.
ADDY
#1
Bump to next free location.
CMPY
QEND(X)
Wrap-around?
BLS
QENQ1
LDY
QSTRT (X)
Reinitialize on wrap-around
«QENQ1
CMPY
QHEAD (X)
Queue already full?
BEQ •
QENQ2
Return with Z=1 if full.
STA
@QTAIL (X)
Else store QDATA.
STY
QTAIL (X)
Update tail.
CLRZ
Set Z:=0 since not full.
QENQ2q
RTS
Return.
Рис. 9.21. (Продолжение.)
'* SUBROUTINE QDEQ — Dequeue one byte from a queue.
* INPUTS
* *
* *
* *
* *
* * *
* *
*
*
IQDTBL — The address of the queue descriptor table for the queue to be manipulated, passed in register X. OUTPUTS
QEMPTY — 1 if the queue is empty, else 0; passed in condition bit Z.
QDATA — The byte dequeued, passed in register A.
Global data, local data — None.
FUNCTIONS
If the queue described by QDTBL is empty, set QEMPTY to T.
If the queue described by QDTBL is not empty, dequeue QDATA and set QEMPTY to 0.
REGISTERS AFFECTED — A, Y, CC
TYPICAL CALLING SEQUENCE
LDX#QWTDequeue abyte into ABYTE.
JSRQDEQ
BEQUNDFLBranch ifqueue is empty.
)STA ABYTE
QDEQ
DDEQ1
QPEQ2
LDY CMPY BEQ LDA ADDY CMPY BLS LDY
STY CLRZ
RTS END
QHEAD (X) QTAIL (X)
QDEQ2 @Y
#1
QEND(X) QDEQ1
QSTRT (X) QHEAD (X)
Get queue head.
Queue empty?
Return with Z=1 if empty.
Read QDATA byte from queue.
Bump to next item in queue. Wrap-around?
Reinitialize on wrap-around.
Save new value of head.
Set Z:=0 since not empty. Return.
Рис. 9.21. (Продолжение.)
. Ознакомьтесь также с разд. 12.3, в котором содержатся сведения относительно соглашений по документированию программного обеспечения, используемых в данном разделе.
РЕКУРСИЯ
Рекурсивные процедуры и функции
Процедура или функция, которая вызывает саму себя, называется рекурсивной. Функцию вычисления факториала языка Паскаль в примере на рис. 9.9 можно переопределить как рекурсивную функцию:
FUNCTION Fact (i: integer) : real;
BEGIN
IF i < = 1 THEN Fact: = 1 ELSE Fact: = i*Fact (i— I);
END;
Существенной здесь является базовая часть, которая определяет параметр Fact(i) равным единице для любого i^l. Для больших значений i параметр Fact(i) определяется произведением i и Fact(i—1). Например, чтобы вычислить параметр Fact (5), следует вначале вычислить Fact (4), зависящий от Fact(3), который в свою очередь определяется Fact (2); последний обусловлен значением Fact(l). Базовая часть обеспечивает то, что в конечном итоге будет достигнуто такое значение i, для которого Fact(i) не зависит от Fact(i—1), следовательно, в этом случае рекурсивные вызовы Fact можно завершить.
Приведенный выше пример иллюстрирует простую рекурсию» использующую процедуру, которая непосредственно вызывает саму себя. Вполне возможно также для процедуры вызывать одну или несколько промежуточных процедур, которые в конце концов вызывают первую процедуру. Такая последовательность вызовов называется косвенной рекурсией, соответствующий пример приводится ниже:
PROCEDURE ProcA(x,y: integer);
BEGIN
РгосВ(а); {Вызов процедуры РгосВ)
END;
PROCEDURE РгосВ(z : integer);
BEGIN
ProcA(b,c); {Вызов процедуры ProcA}
END;
Поскольку язык Паскаль требует определения процедуры до ее вызова, приведенный выше фрагмент программы является некорректным в отношении синтаксиса. Программист должен информировать компилятор о наличии ссылки вперед, помещая следующее объявление перед определением процедуры РгосА:
PROCEDURE РгосВ(z: integer); forward;
Псевдокоманда forward занимает место блока, который обычно требуется в синтаксической диаграмме, приведенной на рис. 9.1. Оператор предупреждает компилятор, что блок, определяющий процедуру РгосВ, поступит позже. Список параметров включает ссылку вперед, поэтому операторы, которые обеспечивают обращение к процедуре РгосВ, можно контролировать и компилировать. Позже тело процедуры РгосВ можно определить обычным образом, за исключением того, что список параметров не повторяется.
Блочно-структурные языки типа Алгол и Паскаль разрешают
рекурсивный вызов всех процедур и функций. Языки неструктурного типа, подобно Фортрану, не допускают рекурсии.
Определение рекурсивной функции, указанной выше, может выглядеть достаточно обоснованным, но итерационное решение (см., например, программу на рис. 9.9) может оказаться более эффективным. Задачи, которые можно достаточно просто решить с использованием итерационных циклов, следует решать именно этим методом, а рекурсии необходимо применять только в тех случаях, когда задачи наиболее четко формируются с использованием рекурсий или они не имеют очевидного итерационного решения.
Рекурсивные подпрограммы
Рекурсию можно использовать в подпрограммах языка ассемблера, но в этом случае накладываются некоторые ограничения на организацию вызова подпрограммы и соглашения по передаче параметров. Не следует хранить адреса возврата, параметры и локальные переменные в специально выделяемых фиксированных ячейках памяти, поскольку эти данные будут стерты при первом же рекурсивном вызове подпрограммы самой себя. Для устранения такой ситуации необходимо назначать при каждом рекурсивном вызове новую область памяти для адреса возврата, параметров и локальных переменных, а затем после возврата из подпрограммы перераспределять эту область памяти, исходя из имеющихся потребностей. Подходящей структурой для хранения этих элементов данных является стек.
Подпрограмму, которая хранит свой адрес возврата, все параметры и локальные переменные в соответствии с соглашением по использованию стека можно вызывать рекурсивно без появления ошибок. Это объясняет, почему процедуры языка Паскаль могут вызывать рекурсивно друг друга, а подпрограммы языка Фортран не могут этого делать вследствие того, что в языке Фортран обычно используются назначаемые фиксированные ячейки памяти для хранения параметров.
Для анализа игры NIM — игры с использованием набора палочек, в которой участвуют два человека, — могут применяться две рекурсивные подпрограммы. Игроки попеременно выбирают палочки из набора; игрок, который берет последнюю палочку, проигрывает. Игра полностью характеризуется двумя параметрами: NHEAP — начальное количество палочек в наборе, NTAKE — максимальное количество палочек, которые игрок может взять при своем очередном ходе; минимальное значение этого параметра равно единице.
Мы хотели бы составить программу, которая определяет при данных параметрах NHEAP и NTAKE, может ли искусный игрок (Р1), ходя первым, всегда выигрывать, делая оптимальные
♦ Подпрограмма P1TURN
определяет, является ли текущая позиция
♦ выигрышной
при данных NTAKE (глобальная переменная) и STIC-
* KSLEFT fпеременная,
переданная в регистр А) при условии, что
* очередной
ход делает
игрок Р1. Подпрограмма P1TURN сохраняет
♦ значения регистров А
и В в начале и восстанавливает их по завер
* шении
работы. Производится возврат результата в разряд уело-
♦ вия Z
: если
позиция выигрышная, Z=1; в противном случае Z=0
P1TURN
PSHS
А, В
Save registers В and A on stack.
TST
А
Any sticks left?
BEQ
WIN
Return with Z=1 if none (we just won) .
LDB
NTAKE
В := maximum # of sticks to take.
BRA
P1L2
Jump into loop.
P1LOOP
JSR
P2TURN
Do we have a winning position?
BEQ
WIN
Found one, mark this a winner.
DECB
Otherwise, try taking another stick.
BEQ
LOSE
Lose if we’ve tried up to NTAKE sticks.
P1L2
DECA ВСЕ
P1L00P'
Also lose if there are no more sticks,
*
LOSE
PULS
А, В
Restore A and В from stack.
CLRZ RTS
Return with Z=0 (not a winner).
WIN
PULS
А, В
Restore A and В frbm stack.
SETZ
Return with Z=1 (a winner),
*
RTS
* Подпрограмма P2TURN определяет, является ли текущая позиция * выигрышной при данных NTAKE (глобальная переменная) и STIC- * KSLEFT (переменная, переданная в регистр А) при условии, что * очередной ход делает игрок Р2. Подпрограмма P2TURN сохраняет ♦ значения регистров А и В в начале и восстанавливает их по завер * шении работы. Производится возврат результата в разряд усло- * вия Z; если позиция выигрышная, Z/1; в противном случае * Z/0. Процедура выхода используется совместно с подпрограм-
* мой
P1TXJRN
P2TURN
FSHS
A, В
Save registers В and A on stack.
TST
A
Any sticks left?
BEQ
LOSE
Return with Z=0 if none (we just lost)•
LDB
NTAKE
В := maximum # of sticks to take.
BRA
P2L2
Jump into loop.
P2LOOP
JSR
P1TURN
Do we have a losing position?
BNE
LOSE
Found one, mark this a loser.
DECB
Otherwise, try taking another stick.
BEQ
WIN
Win if we’ve tried up to NTAKE sticks.
P2L2
DECA
Also win if there are no more sticks.
BCE
P2LOOP
BR
WIN
*
NTAKE-
> RMB
1 .
Global var — max # of sticks to take.
Рис. 9.22. Рекурсивные подпрограммы анализа игры NIM.
ходы. Для того чтобы сформулировать рекурсивный алгоритм, приводящий к такому результату, определим вначале выигрышную позицию для игрока Р1:
Если настала очередь игрока Р1 брать палочку, а в наборе их не осталось, значит, второй игрок (Р2) только что взял последнюю палочку. Эта ситуация является выигрышной для игрока Р1.
Если настала очередь игрока Р1 брать палочку и имеется по крайней мере одна выигрышная позиция среди позиций, получаемых при извлечении из набора палочек, число которых может меняться от 1 до min (NTAKE, STICKSLEFT), то игрок Р1 может выбрать соответствующее количество палочек и в конце концов выиграть, т. е. имеет место выигрышная ситуация для игрока Р1.
Если настала очередь игрока Р2 брать палочку, а в наборе их не осталось, значит, первый игрок (Р1) только что взял последнюю палочку. Эта ситуация не является выигрышной для игрока Р1.
Если настала очередь игрока Р2 брать палочку и в наборе имеется хотя бы одна невыигрышная позиция среди оставшихся позиций (NTAKE, STICKSLEFT), игрок Р2 может выбрать соответствующее количество палочек, чтобы игрок Р1 не выиграл, т. е. ситуация для игрока Р1 не является выигрышной.
Вышеперечисленные шаги 1 и 3 формируют базовые части двух рекурсивных подпрограмм P1TURN и P2TURN, которые вызывают друг друга. Каждая подпрограмма определяет, является ли текущая позиция выигрышной для игрока Р1 при данных NTAKE и STICKSLEFT, принимая во внимание, что очередной шаг выполняется игроком Р1. Подпрограммы анализа игры NIM представлены на языке ассемблера ЭВМ 6809 и приведены на рис. 9.22. Входные и выходные параметры передаются с использованием регистров, а локальные переменные помещаются в стек в начале каждой подпрограммы и восстанавливаются по завершении работы подпрограммы. Программа может установить любое значение параметра NTAKE и вызвать подпрограмму P1TURN с начальным размером набора, содержащимся в регистре А, чтобы определить гарантированный выигрыш искусного первого игрока (Р1):
ILOSE ...IWIN ...ILOSE ...IWIN ...LDA*5Взять максимально 5 палочек за один шагSTANTAKELDA*30Могу я выиграть, если начну с набора из 30 палочек?JSRPITURNBEQIWINLDA*5Взять максимально 5 палочек за один шагSTANTAKELDA*30Могу я выиграть, если начну с набора из 30 палочек?JSRPITURNBEQIWIN
ILOSE ...
IWIN ...
ILOSE ...
IWIN ...
LDA
*5
Взять максимально 5 палочек за один шаг
STA
NTAKE
LDA
*30
Могу я выиграть, если начну с набора из 30 палочек?
JSR
PITURN
BEQ
IWIN
LDA
*5
Взять максимально 5 палочек за один шаг
STA
NTAKE
LDA
*30
Могу я выиграть, если начну с набора из 30 палочек?
JSR
PITURN
BEQ
IWIN
Рекурсивные программы часто выполняют вычисления значительного объема при использовании памяти относительно небольшого объема. Например, подпрограммы для игры NIM не являются длинными, включают только одну глобальную переменную (NTAKE), для них не требуется стековой памяти объемом более 4-NHEAP байт. При вызове для работы с параметрами NHEAP = 30 и МТАКЕ = 5 обе подпрограммы выполняются 1 687 501 раз. Попытайтесь определить сами, выиграл или проиграл игрок Р1 в этом случае.
СОПРОГРАММЫ
Общая структура
До сих пор мы рассматривали подпрограммы в контексте взаимоотношений главной и подчиненной программ: вызывающая (главная) программа производит вызов подпрограммы (подчиненной программы), которая выполняется от начала до конца и передает управление вызывающей программе. В языке Паскаль подпрограммы (процедуры и функции) в такой степени зависят от главной программы, что им не разрешается сохранять свои собственные локальные данные между последовательными вызовами. Сопрограммы позволяют заменить эту структуру набором взаимодействующих программных модулей без указания главного модуля. Приведем постановку задачи, сделанную Флойдом1):
Выполните считывание строк текста, пока не будет обнаружена пустая строка. Устраните лишние пробелы между словами. Напечатайте текст, располагая по 30 символов в строке и не допуская переноса частей слов с одной строки на другую.
Согласно Флойду, неопытные программисты затратят необоснованно много времени для решения этой проблемы с помощью стандартных языков программирования. Благодаря тому что входные и выходные потоки естественно разделяются с помощью уровней итераций, входные и выходные итерационные циклы не перемешиваются; в противном случае могла бы возникнуть путаница по входным и выходным параметрам. Задачу можно решить путем ее декомпозиции на три взаимосвязанные сопрограммы для чтения входных символов, формирования слов и их печати (рис. 9.23). Сопрограмма GetChar считывает входные символы и обнаруживает пустые строки. Сопрограмма GetWord формирует слова и устраняет пробелы, осуществляя
*> Floyd R. W., The Paradigms of Programming, Comm. ACM, 22 8, 455— 460 (Aug. 1979).
прием отдельных символов из сопрограммы GetChar и передачу сформированных слов сопрограмме PrintWord. Сопрограмма PrintWord формирует из слов строки согласно имеющемуся ограничению на длину строки.
Рис. 9.23. Сопрограммы форматирования текста.
Сопрограммы расширенной версии языка Паскаль
Для того чтобы изучить сопрограммы более подробно, воспользуемся новым зарезервированным словом COROUTINE для определения сопрограммы в расширенной версии языка Паскаль
Рис. 9.24. Выполнение двух сопрограмм.
и зарезервированным словом RESUME для возобновления работы сопрограммы. Когда производится вызов сопрограммы в первый раз, то она начинает выполняться с первого оператора. Если сопрограмма вызвана, то выполнение сопрограммы Corl может быть временно приостановлено с помощью оператора RESUME Сог2, который передает управление другой сопрограмме Сог2. При использовании оператора RESUME Corl приостанавливается выполнение сопрограммы Сог2 и продолжается выполнение сопрограммы Corl с того места, где она была прервана в тот момент, когда сопрограмма Corl вызвала сопрограмму Сог2 (рис. 9.24).
На рис. 9.25 определены сопрограммы GetChar, GetWord и PrintWord для форматирования текста1). Важное отличие меж-
^ Поскольку в стандартном языке Паскаль сопрограммы в действительности не реализуются, мы игнорируем синтаксическую ошибку: эти определения сопрограмм содержат ссылки вперед друг на друга.
ду сопрограммами и стандартными процедурами языка Паскаль состоит в том, что сопрограммы должны сохранять значения своих локальных переменных между последовательными вызовами. Так, переменная column в сопрограмме PrintWord «помнит» текущий номер выходной колонки, чтобы корректно обрабатывать очередное слово.
Каждая из сопрограмм, приведенных на рис. 9.25, была составлена независимо, как если бы другие сопрограммы выступали в качестве отдельных подпрограмм. Например, сопрограм-
PROGRAM Format (input, output)
| Программа считывает строки входного текста до тех пор, пока
не будет обнаружена пустая строка. Она устраняет лишние пробелы между словами и упаковывает слова в выхо^пые строки, максимальная длина которых составляет 30 символов; перенос слова не допускается. Слова длиной более 30 символов усекаютсяi
CONST lineLen = 30;
VAR inChar : char; {Passes characters from GetChar to GetWord.}
wordBuf : ARRAY [1..lineLen] OF char; {Buffer for accumulating words and passing them from GetWord to PrintWord.}
wordPnt : integer; {index of last valid character in wordBuf.} blankLine : boolean; {Set true when a blanlf line is read.}
COROUTINE GetChar;
BEGIN
REPEAT {forever}
blankLine := true; read (inChar); {'read* sets eoln true...}
WHILE NOT eoln DO{...at the end of a line.}
BEGIN
blankLine : = false; RESUME GetWord; read (inChar) ;
END;
{A space is needed to flush the last word on a line.}
inChar := ' '; RESUME GetWord;
UNTIL false;
END;
COROUTINE GetWord;
BEGIN
REPEAT {forever}
wordPnt : = 0;
REPEAT {Skip spaces.}
RESUME GetChar; IF blankLine THEN RESUME PrintWord;
UNTIL inChar <> ' •;
REPEAT
IF wordPnt < lineLength THEN
BEGIN
wordPnt := wordPnt + 1;
*wordBuf[wordPnt] := inChar;
END;
RESUME GetChar;
UNTIL inChar = 1 ';
RESUME PrintWord; {Got a word, go print it.}
UNTIL false;
END;
COROUTINE Printword;
VAR column, i : integer*
BEGIN
column := 0;
RESUME GetWord; {Get first word.}
WHILE NOT blankLine DO
BEGIN {Read and print a word.}
{Will the word fit, including a separating space?}
IF column = 0 THEN {Do nothing.}
ELSE IF column + wordPnt + 1 <= lineLength THEN BEGIN write (’ *); column := column+1 END {Start a new line if word doesn't fit.}
ELSE BEGIN writein; column := 0 END;
FOR i: = 1 TO wordPnt DO {Print the current word.}
BEGIN write(wordBuf[i]); column := column+1 END;
RESUME GetWord; {Get next word.}
END;
writein; {Finish last line and return to Main.} END;
BEGIN {Main Program} Printword;
END.
Рис. 9.25. Программа форматирования строк, использующая сопрограммы.
ма GetChar считывает символы и передает их в сопрограмму GetWord; она также преобразует условие конца строки в символ пробела для сопрограммы GetWord. В то же время сопрограмма GetWord для получения символа может производить вызов сопрограммы GetChar из двух различных мест; при этом не имеет, значения тот факт, что выполнение сопрограммы GetChar может возобновляться из двух различных мест.
Сопрограммы GetChar и GetWord содержат бесконечные циклы. Однако из сопрограммы GetChar в сопрограмму PrintWord передается признак пустой строки blankLine, чтобы передать управление в главную программу по окончании обработки всех строк.
Сопрограммы языка ассемблера
Для того чтобы запрограммировать сопрограммы на языке ассемблера, необходимо сохранить «адрес возобновления работы» для каждой сопрограммы. Когда сопрограмма Corl возобновляет работу сопрограммы Сог2, то она должна сохранить текущее значение счетчика команд в ячейке памяти RES1 и выполнить переход по адресу, содержащемуся в ячейке памяти RES2. Далее сопрограмма Corl может возобновить работу путем перехода по адресу, который был сохранен в ячейке RES1.
Если сопрограмма Corl в ЭВМ 6809 вызывает сопрограмму Сог2 с помощью оператора JSR Сог2 и наоборот, то можно использовать следующие операторы для связи двух сопрограмм:
COR1
PULS
Y
Сохранить адрес возобновления работы сопрограммы Сог2 в ячейке RES2
STY
RES2
JMP
@RES1
Перейти по адресу возобновления работы сопрограммы Corl
COR2
PULS
Y-
Сохранить адрес возобновления работы сопрограммы Corl в ячейке RES1
STY
RES1
JMP
@RES2
Перейти по адресу возобновления работы сопрограммы Сог2
RES1
RMB
2
Выделить память для адреса возврата в сопрограмму Corl
RES2
RMB
2
Выделить память для адреса возврата в со-
программу Сог2
Обратите внимание на то, что в командах JMP используется косвенный способ адресации. Теперь остается лишь установить начальные значения величин, сохраняемых в ячейках памяти RES1 й RES2, равными адресам первых выполняемых команд каждой сопрограммы.
Сопрограммы форматирования строк, представленные на рис. 9.25, были модифицированы для ЭВМ 6809 (см. рис. 9.26). В конце программы определена макрокоманда COLINK для формирования связей между сопрограммами. В целом команды связи сопрограмм должны учитывать состояние как сопрограммы, выполнение которой приостанавливается, так и сопрограммы, выполнение которой возобновляется. Например, сопрограмма GetWord может возобновить работу как от сопрограммы GetChar, так и от сопрограммы PrintWord, поэтому необходимо организовать две различные связи. Обратите внимание на то, что имеется только один адрес возобновления работы для каждой сопрограммы.
Применение сопрограмм
Сопрограммы находят наибольшее применение в программах, которые считывают данные, выполняют преобразования и осуществляют выдачу информации (рис. 9.27,а).
По аналогии с электронными устройствами такие программы часто называют фильтрами. Иногда фильтры имеют каскадную структуру. Для поиска орфографических ошибок в текстовом файле могут использоваться фильтры, выполняющие, например, следующие действия:
исключение всех пунктационных символов и повторное форматирование текста так, чтобы каждая строка содержала только одно слово;
исключение из текста всех слов, которые содержат только прописные буквы (считая их акронимами или мнемоническими обозначениями);
преобразование каждой прописной буквы в соответствующую строчную букву;
поиск каждого слова в словаре и выдачу на печать всех слов, которые в нем отсутствуют.
Программа может быть разработана таким образом, что юна будет выполнять эти функции последовательно, формируя три временных файла, которые передают результаты одного фильтра другому (рис. 9.27,6). В другом варианте программа
V
ORG
2000H
SPC
EQU
20H
ASCII space.
CR
EQU
ODH
ASCII carriage return.
LINELN
EQU
30
Maximum output line length.
WRDBUF
RMB
LINELN
Word buffer.
BLANK
RMB
1
Blank-line flag.
COLUMN
RMB
1
Output column number.
XTEMP
RMB
2
Temporary storage for X.
STACK
RMB
20
Stack area.
STACKE
EQU
*
Stack initialization address.
*
*
COROUTINE GetChar —
returns a character in A.
GCHRIN
LDA
#0FFH
Assume we have a blank line Unless
STA
BLANK
we get a nonspace.
GCHR1
JSR
READ
Read a character.
CMPA
#CR
Is it the end of line?
BEQ
GCHR5
CLR
BLANK
No, not a blank line.
GCHR2
JSR
GETWRDG
Give the character to GETWRD...
BRA
GCHR1
...and do some more.
GCHR5
LDA
#SPC
At end of line, force a space...
JSR
GETWRDG
...and give it to GETWRD.
BRA
GCHRIN
Go read more lines.
*
*
COROUTINE GetWord —
puts a word in WRDBUF[1..X] .
GWRDIN
LDX
#0
Set index before start of WRDBUF.
GWRD1
JSR
GETCHR
Get a character.
TST
BLANK ( ,
Hit a blank line?
BEQ
GWRD2
No, continue.
JSR
PRTWRD
Yes, resume PRTWRD.
GWRD2
CMPA
#SPC
Skip over spaces.
BEQ
GWRD1
GWRD3
CMPX
#LINELN
Is there room left in WRDBUF?
BHS
GWRD4
No, ignore character.
ADDX
#1
Yes, put the character in WRDBUF.
STA
WRDBUF-1 (X)
GWRD4
JSR
GETCHR
Get another character...
CMPA
#SPC
...and continue processing until a
BNE
GWRD3
space character is found.
JSR
PRTWRD
Now we have a word, go print it...
BRA
GWRDIN
...and then get some more words.
♦
COROUTINE PrintWord — prints the word in WRDBUF[1•.X]•
PWRDIN
CLR COLUMNSet output column to zero.-
PWRD1
JSR . GETWRDPGet a word.
PWRD5
TSTBLANKHit ablank line?
BNEPWRD9Yep, exit.
TSTCOLUMNNope,are we in themiddleofaline?
BEQPRTBUFPrintword now ifwe1reatcolumnzero
STXXTEMPOtherwise, will the word fit?
LDBXTEMP+1Get the word length (low-order X) ...
ADDBCOLUMN...plus number of characters so far.
CMPB#LINELN-1Will word fit, including a space?
BHIPWRD5Start a new line if it won’t fit.
LDA#SPCOtherwise output a space...
JSRWRITE
INCCOLUMN
BRAPRTBUF...and print the word.
JSRWRITELNPrint CR and LF for a new line.
PRTBUF
CLRCOLUMN
STX XTEMP. Print the word in WRDBUF.
PRTB1
LDY#1
CMPY XTEMP
BHIPWRD1Go process more words when done.
LDAWRDBUF-1(Y)Else print another character...
PWRD9
JSR . WRITE
ADDY #1
INCCOLUMN...and update column # accordingly*
BRAPRTB1
JSRWRITELNPrint CR and LF for a new line.
RTSReturn to main program.
MAIN
LDS #STACKEInitialize SP.
*
COLINK
LDX#GWRDINInitialize coroutine linkage.
STXGWRDRES
LDX#GCHRIN
STXGCHRRES
JSRPWRDINPrint words until blankline found*
JMP1000HReturn tooperatingsystem.
MACROFROM,TOCoroutinelinkages.
*
GETCHR
PULS Y STYFROM
JMP@TO
ENDM
COLINK GWRDRES,GCHRRES
GETWRDG
COLINK GCHRRES,GWRDRES.
CETWRDP
COLINK PWRDRES,GWRDRES
PRTWRD
COLINK GWRDRES,PWRDRES
GCHRRES
RMB 2Resumption address for GetChar.
GWRDRES
RMB 2Resumption address for GetWord.
PWRDRES
RMB 2Resumption address for Printword.
Рис. 9.26. Версия программы форматирования строк для ЭВМ 6809.
может быть организована в виде четырех сопрограмм (рис. 9.27, ^). В первом случае отдельные фильтры могут выполняться в разнос время, и, следовательно, каждый из них может занимать небольшой объем памяти. Во втором случае структура сопрограмм позволяет избежать использования дополнительной памяти для файлов и лишнего расходования времени обработки.
Рис. 9.27. Фильтры и сопрограммы:
• — простой фильтр; б —каскад фильтров, использующих промежуточные файлы; в — эквивалентная структура сопрограмм.
связанного с чтением и записью временных файлов, хотя, возможно, и за счет увеличения требуемого объема^ памяти для программ.
УПРАЖНЕНИЯ
Преобразуйте программу сжатия пробелов (рис. 9.6) таким образом, чтобы цепочки из одного или двух пробелов не подвергались никаким преобразованиям, а цепочки, содержащие более 26 пробелов, преобразовывались в два (или более) кода вида #х.
Какой вид параметров (значащие или переменные) используется стандартными процедурами read и write языка Паскаль?
Объясните, что произойдет при вызове процедуры Swap (i,next[ij), если используются переменные параметры языка Паскаль и значащие параметры языка Фортран?
Перечислите имя, тип и соглашение пэ передаче для каждого входного и выходного параметра подпрограмм, приведенных на рис. 5.14, 5.21, 7.18, 8.3, 8.9, 8.11, 9.13 и 9.14.
Представьте в табличной форме преимущества и недостатки каждого соглашения по передаче параметров, обсужденного в разд. 9.3.
Предположите, что все подпрограммы в программе совместно используют общую область параметров. Какие проблемы возникают в этом случае, если подпрограммы являются вложенными?
Сравните, каким образЬм от числа различных подпрограмм и от числа- различных вызывающих программ зависит выбор методов статической пере* дачи параметров (выделенные ячейки памяти для каждой подпрограммы — область параметров для каждой вызывающей программы).
Измените подпрограмму DIVIDE (рис. 9.15) таким образом, чтобы ее можно было хранить в постоянном запоминающем устройстве. ^Указание» Используйте счетчик CNT.) Модифицируйте вызывающую программу (рис.. 9.16) для работы с новой подпрограммой.
Модифицируйте подпрограмму DIVIDE (рис. 9.15) для использования' линейной области параметров. Измените вызывающую программу (рис. 9.16> для работы с новой подпрограммой.
Предположите, что последние три машинные команды подпрограммы (рис. 9.18) заменены одной новой командой RTS 1. Определите требуемую операцию новой 2-байтовой команды RTS п, где п — 8-разрядная целая величина без знака.
Каким образом вы классифицировали бы параметры, используемые в подпрограммах обработки очередей (рис. 9.21)?
Объясните, почему можно исключить две команды CLRZ в подпрограммах, приведенных на рис. 9.21, без всякого ущерба для их работы?
Включите подпрограмму QROOM (и комментарий) в подпрограммы обработки очередей (рис. 9.21), входными параметрами которых являются 16- разрядная целая величина без знака N и адрес таблицы описания очереди, а выходной параметр — один разряд, указывающий наличие или отсутствие в указанной очереди места для дополнительных N байт. ‘
Покажите, что нет необходимости загружать данные в регистр А и извлекать их оттуда в подпрограммах NIM (рис. 9.22), если содержимое регистра уменьшается в соответствующих местах программ.
Напишите версии подпрограмм NIM на языке Паскаль, используя рис. 9.22.
Выполните трансляцию подпрограмм NIM, представленных на рис. 9.22,. на язык ассемблера вашей вычислительной машины. Напишите и выполните прогон главной программы, которая вызывает подпрограммы для всех значений NTAKE в диапазоне 2—6 и для значений NHEAP в диапазоне 1—30* и выдает результаты на печать (выигрыш или проигрыш) для каждой возможной начальной ситуации.
Изучите результаты, полученные при выполнении предыдущего упражнения, и предложите простую формулу, которая определяет, является ли начальная позиция выигрышной для опытного игрока, делающего первый ход^ при данных NTAKE и NHEAP. Попытайтесь доказать корректность вашей формулы неформальными методами или методом математической индукции^ 9.18. Задача о восьми ферзях. Эта задача является классической в вычислительной математике. Ее решение состоит в таком размещении восьми ферзей на шахматной доске размером 8X8, при котором ни один ферзь не угрожает другому. (Ферзь угрожает любой фигуре, находящейся на одной с ним горизонтали, вертикали или диагонали. Следовательно, любое решение предполагает размещение каждого ферзя на различных горизонталях, вертикалях и диагоналях.) Существует 92 различных решения этой проблемы (включая симметричные решения), которые можно найти, используя метод проб и ошибок. Разработайте программу на языке Паскаль или на языке ассемблера, предусматривающую все решения этой задачи. Основная структура данных в программе — булев массив 8X8, представляющий собой поля шахматной доски. Ключевым моментом программы является рекурсивная процедура try (col: integer), которая пытается разместить 9—col ферзей по вертикалям от col до 8; п*ри получении решений последние выводятся на печать. Решение состоит в поиске тех клеток по вертикали col, которые не находятся под угрозой. Для каждой такой клетки программа размещает ферзя на доску и вызывает процедуру try(col+l) до тех пор, пока не выполнится условие col=8, при соблюдении которого решение выдается на печать. Главная.
программа выполняет начальную установку массива полей шахматной доски и вызывает процедуру try(l).
ФЛ9. Для программы подсчета единиц в упр. 7.6 требуется наличие таблицы N1S[O..255], включающей 256 целых чисел, где NlS[i] содержит количество «единиц в 8-разрядном двоичном представлении величины i. В представленной ниже программе на языке Паскаль используется рекурсивная процедура для инициализации табличного массива. Выполните трансляцию этой программы на язык ассемблера вашей вычислительной машины.
PROGRAM CountOnesInNonnegativeNumbers (input,output);
VAR inNum, ent : integer; N1S : ARRAY [0..255] OF integer;
PROCEDURE Cntlnit (weight, index, ones : integer);
BEGIN
IF weight=256 THEN N1S[index] := ones
ELSE BEGIN Cntlnit(weight*2,index,ones);
. Cntlnit(weight*2,index+weight,ones+1) END;
END;
BEGIN {Main program}
Cntlhit (1,0,0); {initialize N1S array.} read(inNum);
WHILE inNum>=0 DO {Process nonnegative input numbers.} BEGIN ent := 0;
WHILE inNum>0 DO
BEGIN ent := ent + N1S[inNum MOD 256]; inNum := inNum DIV 256END;
writein(’The number of ones in ’,inNum,1 is 1,cnt); read(inNum);
END;
END. (All done, bye.}
Ф.20. Рекурсивной макрокомандой является макрокоманда, которая вызывает саму себя. В макроассемблере, допускающем рекурсию, можно написать рекурсивную макрокоманду для начальной установки таблицы N1S (см. предыдущий пример) во время загрузки. Примером такой макрокоманды слу- жит следующий фрагмент программы:
CINIT
MACRO WT,CNT
IFNE WT,256
CINIT WT*2,CNT
CINIT WT*2,CNT+1
ENDIF
IFEQ WT,256
FCB CNT
ENDIF
ENDM
N1S
CINIT 1.0Инициализировать таблицу размером
256 байт
Хотя приведенный выше фрагмент программы формирует таблицу раз* мером 256 байт, при этом он не обеспечивает, чтобы элемент таблицы NlS[i] был равен количеству единиц в двоичном представлении величины L Объясните почему и напишите макрокоманду с использованием рекурсии, которая бы правильно инициализировала массив N1S.
Перепишите программу, приведенную на рис. 9.25, на стандартном языке Паскаль как главную программу, которая выполняет функцию GetWord„ и две процедуры GetChar и PrintWord. Опишите изменения, которые следует внести при отсутствии сопрограмм. •
Получение сопрограмм, которые функционируют совместно с главной программой, очень сложная задача. Объясните, почему главная программа, представленная на рис. 9.25, вызывает процедуру PrintWord с помощью* стандартного процедурного оператора языка Паскаль, а не оператора RESUME.. Объясните, почему главная программа на языке ассемблера (рис. 9.27> непосредственно вызывает процедуру PWRDIN, вместо того чтобы использовать связь сопрограмм. *
Вставьте сопрограмму Translate, обрабатывающую последовательности escape вида 'lx', между процедурами GetChar и GetWord (рис. 9.25). Обработка последовательности escape зависит от следующих символов х:
1—9 Обеспечивается вставка от 1 до 9 «непробиваемых» пробелов в выходной поток. Эти пробелы воспринимаются как печатные символы.
! Обеспечивается вставка символа «I» в выходной поток.
< Обеспечивается преобразование прописных букв в строчные до очередного уведомления.
> Приостанавливает преобразование прописных букв до очередного уведомления. 1
и Преобразует очередной символ в строчную букву, если это буква.
Если символ х не является одним из указанных выше символов, то последовательность 'lx' игнорируется.
Выполните трансляцию представленной выше сопрограммы Translate на язык ассемблера ЭВМ 6809.
Почему PWRDRES не инициализируется главной программой (рис. 9.27)?
Вариантом установления связей сопрограмм (рис. 9.27) является использование операторов, подобных GWRDRES FCW GWRDIN. Какая ошибка кроется в таком подходе?
Покажите, каким образом адрес возврата, хранимый в верхушке стека^ может быть использован для связи двух сопрограмм, выполняемых на ЭВМ 6809. Объясните, почему этот способ непригоден при наличии трех и более сопрограмм.
Продумайте структуру связи сопрограмм для ЭВМ 6809, которая позволит каждой сопрограмме располагать независимым стеком, доступным для указателя стека (SP); при этом адрес возврата каждой приостановленной сопрограммы должен всегда находиться в верхушке ее собственного стека.
ЛИТЕРАТУРА
Исторические сведения и вопросы, касающиеся разработки подпрограмм,, сопрограмм и связанных с ними концепций, изложены в работе Кнут» (Knuth Е., Fundamental Algorithms, 2nd ed., Addison-Wesley, 1973, pp. 225— 227). Команды для вызова подпрограмм использовались во всех ЭВМ первого поколения, однако только в 60-е годы для хранения адресов возврата стали использоваться стеки магазинного типа [Lonergan, King, Design of the- В 5000 System, Datamation, 5, 7, 28 (May 1961); Bell, Newell, Computer Structures, McGraw-Hill, 1971].
Способы передачи параметров в языках высокого уровня подробно описаны в книге трех американских ученых (Organick, Forsythe, Plummer, Program* 24е
ming Language Structures, Academic Press, 1978). Вопрос о соответствии ллежду соглашениями по передаче параметров и операциями и распределением памяти во время выполнения программ обсужден в работе Гриса (Gries D., Compiler Construction for Digital Computers, Wiley, 1971).
Архитектура ЭВМ В 5000 обеспечивает передачу параметров с использованием стека; Алгол и другие подобные языки высокого уровня сделали возможным широкое использование стековой памяти. Сравнительно недавно бы- -ли разработаны специальные команды в вычислительных машинах новой структуры для обеспечения передачи параметров в стек (команда RET п в ЭВМ 8086 и команды LINK и UNLK в ЭВМ 68000).
Ряд работ посвящен рекурсивным алгоритмам и процедурам (Barron D. W., Recursive Programming Techniques, American Elsevier, 1968; Organick, Forsythe, Plummer, Programming Language Structures, Academic Press, 1978).
Сопрограммы и их взаимосвязь с многопроходными алгоритмами описываются в упомянутых здесь книгах Кнута и Оргеника и др. Термин «сопрограмма» впервые использовал Конвей [Conway М. Е., Design of a Separable Transition-Diagram Compiler, Comm. ACM, 6, 7, 396—408 (July 1963)]. Однако Кнут обнаружил, что подобное понятие упоминалось еще в 1954 г. как «программная накладка» при описании вычислительной системы UNIVAC.
Много примеров программ-фильтров приведено в работе Кернигена и Плоджера (Kernighan, Plauger, Software Tools, Addison-Wesley, 1976). Идея каскадных фильтров впервые была реализована в операционной системе UNIX, разработанной для ЭВМ PDP-11 и других вычислительных машин, где эта программная структура называлась «потоком». «Потоки» операционной системы UNIX позволяют пользователю эффективно увязывать сопрограммы во время выполнения их на вычислительной машине.
10
ОРГАНИЗАЦИЯ ВВОДА-ВЫВОДА
В предыдущих главах мы отмечали, что за всю историю развития вычислительных систем их базовая организация не изменилась сколь-нибудь значительно, хотя благодаря технологическим усовершенствованиям электронные вычислительные машины стали более быстродействующими, более дешевыми и мощными при одновременном уменьшении габаритов. Из трех главных подсистем ЭВМ подсистема ввода-выв9да претерпела наиболее значительные изменения благодаря применению ЭВМ в самых разнообразных областях человеческой деятельности и появлению сотен различных устройств, являющихся частью вычислительных систем. Мог ли кто-либо из проектировщиков вычислительных систем в 50-е годы предположить, что шире, чем в какой-нибудь другой области, ЭВМ будут применяться в автомобилях щ одним из наиболее часто используемых устройств вывода станет топливный карбюратор!
Однако несмотря на столь широкое распространение устройств ввода-вывода, техника их подключения к вычислительным системам остается пока стандартной. Для программиста, знакомого с основными принципами организации ввода-вывода (мы их рассмотрим в данной главе), не представляет труда изучить характеристики какого-либо нового устройства и запрограммировать «общение» ЭВМ с ним. Примеры, которые мы приводим в этой главе, являются в некоторой степени упрощенными: в действительности характеристики устройств ввода-вывода и средства их сопряжения с различными ЭВМ меняются в широких пределах.
ПОДСИСТЕМА ВВОДА-ВЫВОДА
Подключение подсистемы ввода-вывода к процессору
В разд. 1.3 мы показали, что базовая организация ЭВМ включает три подсистемы — процессор, память и ввод-вывод. Подсистема ввода-вывода подключается к процессору посредст-
вом шины ввода-вывода (рис. 10.1). Как и шина памяти, шина ввода-вывода содержит адресные линии, линии данных и линии управления. Адресные линии позволяют программе выбирать конкретное устройство из различных устройств ввода-вывода, подключенных к системе, тогда как линии данных предназначены для передачи собственно данных.
Количество линий, входящих в состав шины ввода-вывода и шины памяти, может быть разным. Например, шина памяти
блок центрального процессора
Рис. 10.1. Подсистема ввода-вывода.
системы Z8000 состоит из 23 адресных линий и 16 линий данных, в то время как шина ввода-вывода этой же системы содержит 16 адресных линий и либо 8, либо 16 линий данных (в зависимости от конкретной системы). Шина ввода-вывода и шина памяти логически независимы, несмотря на то, что некоторые линии (например, адресные) используются ими совместно. Численно равные адреса на различных шинах означают разные объекты, поскольку управляющий сигнал процессора отличает операции с памятью от операций ввода-вывода. Например, адрес, передаваемый по шине ввода-вывода, который обозначен цифрой 5, никак не связан с ячейкой памяти под номером 5.
Устройства и интерфейсы
Подсистема ввода-вывода (рис. 10.1) содержит как устройства ввода-вывода, так и интерфейсы. Устройство ввода-вывода, или периферийное устройство, выполняет определенные
функции ЭВМ. Интерфейс устройства ввода-вывода управляет действиями периферийного устройства в соответствии с командами процессора, преобразует данные из внутреннего представления в форматы, требуемые для конкретных устройств, и выполняет обратное преобразование. Как показано на рис. 10.1, периферийные устройства часто размещаются отдельно от процессора, а интерфейс почти всегда объединяется с процессором и памятью в «блок центрального процессора».
Клабиатура
Рис, 10.2. Клавиатура и интерфейс.
Существует много различных периферийных устройств, преобразующих машинные данные в форму, удобную для использования во внешней среде. Среди таких устройств можно отметить дисплеи, печатающие устройства, графопостроители, цифро-аналоговые преобразователи, механические реле, а также карбюраторы. Другие устройства выполняют преобразование данных из формы, в которой они выражены во внешней среде, во внутреннее представление ЭВМ. К этим устройствам относятся клавишные пульты, сканирующие устройства считывания текста, аналого-цифровые преобразователи, механические переключатели и аварийные детекторы. Некоторые устройства, называемые устройствами памяти большой емкости, служат только для хранения данных, используемых в дальнейшем. Они включают магнитные диски и ленты.
Иногда невозможно провести четкую границу между интерфейсом и управляемым им устройством. Например, на рис. 10.2 показана схема подключения простой механической клавиатуры. Шифратор преобразовывает сигнал, возникающий в результате механического нажатия клавиши, в 7-битовое число в коде ASCII (см. приложение А). По запросу процессора интерфейс шины может поместить это число на шину ввода-вывода. Из этого следует, что интерфейс устройства физически состоит из
двух блоков: шифратора и интерфейса шины. Однако типичным вариантом является такой, когда шифратор размещается вместе с клавиатурой. В этом случае для подключения шифратора к интерфейсу шины, расположенному в блоке центрального процессора, требуется меньшее число линий. Большинство проектировщиков ЭВМ при этом могли бы сказать, что блок шифратора является частью клавиатуры, а интерфейс устройства ввода-вывода включает только блок интерфейса шины. Следует заметить, что четкое определение этой границы не столь уж важно для программ ввода-вывода, реализуемого с использованием клавиатуры. Более существенным является структура порта, используемая для программирования, которая обсуждается в следующем подразделе.
Порты ввода-вывода
Порт ввода-вывода (или регистр ввода-вывода) является частью интерфейса устройства ввода-вывода и представляет собой группу разрядов, значение которых доступно процессору во
7 6 0
Рис. 10.3. Порт ввода-вывода для клавиатуры.
время операций ввода и вывода данных. Структура 8-разряд- ного порта ввода-вывода для случая использования клавиатуры (рис. 10.2) показана на рис. 10.3. Самый старший разряд порта всегда содержит 0. В остальных разрядах KBDATA находятся данные, выводимые из шифратора, т. е. комбинация 7-битового кода ASCII, соответствующая нажатой клавише, или число 0000000, если ни одна клавиша не нажата.
Для того чтобы прочитать данные с клавиатуры, показанной на рис. 10.2, программа должна выполнить команду, которая передает содержимое KBDATA в один из регистров процессора. Как только данные оказываются в процессоре, ими можно манипулировать как любыми другими данными. Хо^я интерфейс записывает данные с клавиатуры в порт KBDATA, процессор может только считывать данные из порта. Любые попытки со стороны процессора записать данные в KBDATA не приводят ни к какому результату. Поэтому KBDATA можно назвать «портом ввода».
На рис. 10.4, а показано очень простое устройство вывода, которой интерпретирует байт как две 4-битовые цифры числа в двоично-десятичном представлении, и выводит эти цифры на два семиэлементных цифровых индикатора. Структура соответ-
ствующего 8-разрядного порта DIGOUT изображена на рис. 10.4,6. Для того чтобы вывести на индикаторы две цифры, процессор должен передать 8-битовую величину в порт DIGOUT. В этом случае DIGOUT является «портом вывода», процессор может только осуществлять запись в этот порт, а попытка считать из него данные приводит к неопределенному результату.
Существует важное различие между портами ввода и вывода, описанными выше. Порт ввода KBDATA содержит мгновен-
Интерфейс шиныИнтерфейс шиныШина ввода-вывода
Интерфейс шины
Интерфейс шины
L^ DIGOUT
I I (только запись)
[Двоично-десятич
ная цифра младшего разряда
—:Двоично-десятичная
цифра старшего
еразряда
Рис. 10.4. Семиэлементный цифровой индикатор:
в — устройство и интерфейс; б — фрукту* порта, используемая для программирования.
ное значение, выдаваемое шифратором. Содержимое этого порта меняется, как только клавиша нажимается или освобождается. Поэтому KBDATA не является регистром в полном смысле слова, так как он не способен хранить информацию; скорее, это буфер между шифратором и шиной ввода-вывода. Что касается выходного порта DIGOUT, то он является регистром в истинном значении этого слова. После того как процессор записал величину в DIGOUT, ее значение не меняется до следующей операции записи. Аппаратный интерфейс специально спроектирован именно таким образом. Иначе процессор был бы вынужден непрерывно записывать одно и то же значение в DIGOUT, чтобы обеспечить постоянное высвечивание этого значения на индикаторе.
Клавиатура и индикатор являются очень простыми примерами работы устройств ввода-вывода. Далее мы опишем интерфейсы ввода-вывода, управляющие устройствами ввода-вывода более сложным способом.
ПРОГРАММИРОВАНИЕ ВВОДА-ВЫВОДА
До сих пор мы ничего не говорили о том, каким образом происходит передача данных из порта ввода-вывода в регистры процессора и в обратном направлении. В этом разделе обсуждаются два способа, которые используются в процессорах для передачи данных при выполнении операций ввода-вывода.
Изолированный ввод-вывод
В случае изолированного ввода-вывода доступ к портам осуществляется с помощью специальных команд ввода-вывода. Например, система Z8000 располагает двумя командами для передачи содержимого 8-разрядного порта в регистры процессора и обратно:
INB гп.рп REG[rn] := IPORT[pn];
OUTB pn,rn OPORT[pn] := REG[rn];
158 7430КОП | гл |—рп158 7430КОП | гл |—рпЗдесь IPORT— массив из 216 8-разрядных входных портов, OPORT — массив из 216 8-разрядных выходных портов, REG — массив из шестнадцати 8-разрядных регистров процессора,
158 7430
КОП | гл |—
рп
158 7430
КОП | гл |—
рп
Вспомогательный ' операнд
Рис. 10.5. Формат команд INB и OUTB для системы Z8000.
гп—4-битовый номер регистра, рп — 16-битовый номер порта, содержащиеся в команде. Таким образом, длина каждой команды INB и OUTB—два слова. Как показано на рис. 10.5, первое слово содержит код операции и номер регистра, второе слово — номер порта.
Подобно командам загрузки и хранения, команды INB и OUTB осуществляют передачу данных лишь с той разницей, что они имеют доступ к массивам портов ввода-вывода, а не к массивам байтов памяти. Так как основная память и порты ввода- вывода подключены к разным шинам, «адресные пространства» для команд ввода-вывода и команд обращения к памяти различны даже тогда, когда в том и другом случае используются 16битовые адреса.
Количество портов ввода-вывода не всегда определяется количеством возможных адресов. В действительности малые ЭВМ имеют гораздо меньше чем 216 и даже 28 портов ввода- вывода: обычно их число варьируется от 10 до 50.
На рис. 10.6 показана несложная программа для системы Z8000, реализующая изолированный ввод-вывод. При чтении этой программы необходимо помнить, что суффикс «В» в командах Z8000 означает операции над байтами, а не над содержимым регистра В. Программа считывает две десятичные цифры.
* Считать две десятичные цифры с клавиатуры и вывести их на се-
* миэлемеитные цифровые индикаторы. Нецифровые символы иг- * норировать
KBDATA
EQU
5
Keyboard input port number.
DIGOUT *
EQU
7
Seven-segment display port number»
*
First,
a subroutine to read one decimal digit, convert:
it to
4-bit BCD, and
return the result in RLO.
RDDIG
INB
RLO, KBDATA-
Read current character.
TESTB
RLO
Set condition bits (INB doesn’t).
JR
EQ,RDDIG ^
Wait for a key to be pressed.
WAITUP
INB
RHO,KBDATA
Now the,good character is in RLO,
TESTB
RHO
keep reading keyboard data into RHO
JR
NE,WAITUP
and wait for the key to be released.
CMPB
RL0,#30H
Now, was it a valid decimal digit?
JR
LT,RDDIG
Not if it’s less th^n ASCII 'O' ...
CMPB
RL0,#39H
JR
GT,RDDIG
... or greater than ASCII '9'.
SUBB
RLO,#ЗОН
.OK, convert ASCII »0'-'9' to 0-9.
*
RET
Done, return.
*
Now, the main program
DIGDSP
CALL
RDDIG
Read high-order decimal digit into RLO.
SLLB
RLO,#4
Shift left 4 bits
LDB
RL1,RL0
and save in RL1.
CALL
RDDIG
Read low-order decimal digit.
ORB
RL1,RL0
Merge with high-order digit.
OUTB
RL1,DIGOUT
Seno to seven-segment display.
JR
DIGDSP
Do another pair of digits.
END
DIGDSP
Рис. 10.6. Программа ввода с клавиатуры и вывода на индикатор для системы Z8000.
набранные на клавиатуре, и высвечивает их на семиэлементных цифровых индикаторах до тех пор, пока не будет введена следующая пара цифр.
Подпрограмма RDDIG считывает символы с клавиатуры, проверяя их соответствие десятичным цифрам — одному из символов ряда 0, ..., 9. Как только найдена десятичная цифра, она преобразовывается в 4-битовое число, которое размещается в регистре RL0. Единственным способом определения момента нажатия клавиши является непрерывное чтение подпрограммой RDDIG порта KBDATA до тех пор, пока его содержимое не примет значение, отличное от нуля. Как только клавиша нажата, подпрограмма ждет ее отжатия, прежде чем продолжить работу. В противном случае при следующем входе в подпрог-
рамму предыдущее нажатие на клавишу было бы ошибочно принято за новое.
Главная программа принимает от RDDIG пары двоично-десятичных цифр и выводит их на два семиэлементных цифровых индикатора. Отметим, что для загрузки 8-битовой величины в порт DIGOUT требуется только одна команда OUTB. Как только процессор поместит какое-то число в DIGOUT, значение этого числа появляется на индикаторе и высвечивается там до тех пор, пока процессор не загрузит снова порт DIGOUT.
Теперь остановимся на вопросе распределения времени при вводе-выводе. В приведенном выше примере процессор тратит почти все свое время на выполнение первых трех команд подпрограммы RDDIG, ожидая, пока кто-нибудь не нажмет клавишу. На выполнение трех последующих команд, связанных с ожиданием освобождения клавиши, тратится сравнительно мало времени. На реализацию остальной части подпрограммы и главной программы расходуется еще меньше времени, поскольку выполнение одной команды занимает ~ 1 мкс, а максимальная скорость нажатия клавиш составляет 5 раз/с. Из-за огромного расхождения в скорости маловероятно, чтобы программа «пропустила» хотя бы одно нажатие клавиши. Однако в некоторых, более критичных в отношении времени случаях программист должен тщательно анализировать характеристики ввода-вывода и программу, чтобы установить, соответствует ли данная программа применяемому способу ввода-вывода.
Кроме того, можно использовать ЭВМ, бесполезно расходующую свое время на ожидание пока кто-нибудь не нажмет клавишу, для решения некоторых задач, например для вычисления числа л с точностью до миллионной цифры после запятой. В гл. 11 будет показано, как можно избежать бесполезной траты времени, используя прерывание для инициирования программ ввода-вывода только в тех случаях, когда известно, что произошло событие ввода-вывода.
Ввод-вывод, организованный по аналогии с обращением к памяти
В предыдущем разделе было отмечено, что шина ввода-вывода схожа с шиной памяти и что команды ввода-вывода подобны командам обращения к памяти. Ввод-вывод, организованный по аналогии с обращением к памяти, использует это сходство, что практически выражается в отказе от отдельной шины ввода-вывода и команд ввода-вывода.
На рис. 10.7 показана аппаратная реализация ЭВМ с вводом-выводом, организованным по аналогии с обращением к памяти. Как основная память, так и порты ввода-вывода связаны с процессором через общую шину памяти и ввода-вывода*
Каждый порт ввода-вывода имеет адрес в пространстве адресов* основной памяти процессора. Порт ввода допускает выполнение любой команды, которая осуществляет чтение по его адресу, порт вывода — любой команды, которая производит запись по* его адресу. Обычно системный проектировщик выделяет часть адресного пространства для портов ввода-вывода, например^ 4К байт. Однако теоретически порт ввода-вывода может размещаться по любому адресу, если указанная ячейка памяти не занята.
Рис. 10.7. Структура ввода-вывода, организованного по аналогии с обращением к памяти.Рис. 10.7. Структура ввода-вывода, организованного по аналогии с обращением к памяти.
Рис. 10.7. Структура ввода-вывода, организованного по аналогии с обращением к памяти.
Рис. 10.7. Структура ввода-вывода, организованного по аналогии с обращением к памяти.
В любом процессоре можно использовать способ ввода-вывода с обращением к памяти, если проектировщик аппаратуры системы привяжет порты ввода-вывода к шине основной памяти. Например, если бы разработчики системы Z8000 решили воспользоваться этим' способом, порты ввода-вывода необходимо» было бы соединить с шиной памяти, а команды INB и OUTB нужно было бы заменить следующим образом:
LDBrn,pnREG[rn] :=MEM[pn];{INB}
STBrn,pnMEM[pn] := REG[rn];{OUTB}
где pn — адрес памяти в диапазоне, отведенном для портов ввода-вывода. Разработчик как бы обманывает процессор, за
ставляя его выполнять операцию с портом ввода-вывода, тогда как процессор работает так, как если бы он имел дело с бай
тами памяти в указанном диапазоне.
Ввод-вывод с обращением к памяти необходим для тех процессоров, в которых не предусмотрено специальных команд вво-
да-вывода. Первой мини-ЭВМ, использующей ввод-вывод с обращением к памяти, была PDP-11. Этот же способ применен в процессорах 68000, 6809 и 9900, описанных в части III книги.
* Чтение двух десятинных цифр с клавиатуры и вывод их на семиэ- * лементные цифровые индикаторы. Нецифровые символы игно- * рировать
KBDATA EQU0F005HKeyboard input port address.
DIGOUT EQU0F007HSeven-segment display port address.
* Подпрограмма чтения одной десятичной цифры, преобразовав * ния ее в 4-6итовое двоичное число и размещение результата
* в аккумуляторе А
RDDIG
LDA
KBDATA
Read current character.
BEQ
RDDIG
Wait for a key to be pressed.
WAITUP
TST
KBDATA
Now the character is in A,
BNE
WAITUP
wait for the key to be released.
СМРА
#30H
Now, was it a valid decimal digit?
BLT
RDDIG
Not if it’s less than ASCII ’O’ ...
СМРА
#39H
BGT
RDDIG
... or greater than ASCII '9'.
SUBA
#30H
OK, convert ASCII ’O’-19* to 0-9.
RTS
Done, return.
*
*
Now,
the main
program.
DIGDSP
JSR
RDDIG
Read high-order decimal digit into A.
ASLA
Shift left 4 bits ...
AS LA
ASLA
ASLA
STA
HIDIG
... and save in HIDIG.
JSR
RDDIG
Read low-order decimal digit.
ORA
HIDIG
Merge with high-order digit.
STA
DIGOUT
Send to seven-segment display.
BRA
DIGDSP
Do another pair of digits.
HIDIG
RMB
1
Storage for high-order digit.
END
DIGDSP
Рис. 10.8. Программа ввода с клавиатуры и вывода на индикатор для системы 6809.
Ввод-вывод, организованный по аналогии с обращением к памяти, имеет следующие преимущества:
Нет необходимости в применении специальных кодов операций или схем процессора для команд ввода-вывода.
Все команды обращения к памяти, а нс только загрузки и хранения могут быть использованы для манипулирования портами ввода-вывода.
Число доступных адресов портов ввода-вывода фактиче* •ски не ограничено.
Структура шины упрощена.
. Этому способу ввода-вывода присущи также недостатки:
Используется часть адресного пространства памяти.
Интерфейсы могут иметь более сложную схему для распознавания более длинных адресов.
Команды обращения к памяти могут выполняться медленнее, чем оптимизированные команды ввода-вывода.
В будущих системах объединение памяти с вводом-выводом может препятствовать реализации некоторых преимуществ [см. Peuto В., Architecture of a New Microprocessor, Computer (Febr. 1979)].
Интерфейс шиныИнтерфейс шиныааРис. 10.9. Порт ввода-вывода с обратной а — интерфейс; б — структура порта.Рис. 10.9. Порт ввода-вывода с обратной а — интерфейс; б — структура порта.74 3 О74 3 Омладшего разрядамладшего разрядаДВоично-десятичная цифра5 старшего разрядаДВоично-десятичная цифра5 старшего разрядаШина ввода-ВыВода
Интерфейс шины
Интерфейс шины
а
а
Рис. 10.9. Порт ввода-вывода с обратной а — интерфейс; б — структура порта.
Рис. 10.9. Порт ввода-вывода с обратной а — интерфейс; б — структура порта.
74 3 О
74 3 О
младшего разряда
младшего разряда
ДВоично-десятичная цифра
5 старшего разряда
ДВоично-десятичная цифра
5 старшего разряда
связью:
На рис. 10.8 показана программа чтения с клавиатуры ж вывода на дисплей, приведенная на рис. 10.6, переписанная для системы 6809, использующей ввод-вывод с обращением к. памяти. Вместо команд INB и OUTB для изолированного ввода-вывода здесь используются команды LDA и STA. В системе- 6809 имеется одно усовершенствование: команда TST подпрограммы RDDIG проверяет содержимое KBDATA, не помещая его в аккумулятор. Программа же для Z8000 при проведении; аналогичной проверки должна загрузить значение KBDATA в> регистр.
Операции чтения и записи по конкретному номеру порта ввода-вывода полностью независимы в отличие от обычных операций обращения к памяти, когда операция чтения позволяет извлечь из памяти последнюю записанную величину. В тоже время, как можно заметить, назначение номеров портам ввода-вывода полностью определяется разработчиком аппаратуры. Так, разработчик, которому захотелось бы подшутить над программистом-новичком, мог бы назначить порту ввода KBDATA и порту вывода DIGOUT один и тот же номер (например, 5). Все будет прекрасно до тех пор, пока новичок, к своему огром-
ному огорчению, не столкнется с этим очевидным противоречием.
Традиционным является назначение одинаковых номеров портам тогда, когда функции или назначение портов ввода и вывода как-то соотносятся между собой. Например, проектируя порт вывода, опытный разработчик присвоит его номер порту ввода, единственным назначением которого является чтение данных, хранимых в порте вывода, как показано на рис. 10.9. Отметим, что в этом случае мы используем одно и то же имя как для порта ввода, так и для порта вывода. Объединенный порт ввода-вывода больше похож на ячейку памяти, поэтому программа может осуществить чтение порта ввода, чтобы проверить содержимое порта вывода. Это особенно важно для составления тестирующих программ, а также некоторых прикладных программ.
ПРОТОКОЛЫ ВВОДА-ВЫВОДА
Клавиатура и дисплей, описанные в предыдущем разделе, являются очень простыми устройствами для управления. В общем случае устройства ввода-вывода используют так называемый протокол синхронизации (в отечественной литературе используется термин «временная диаграмма») для соответствующей операции.
Операции ввода
Протокол синхронизации для типичного устройства ввода показан на рис. 10.10. Когда процессор готов для чтения данных, он посылает импульс по линии управления START I/O. Интерфейс устройства реагирует на этот импульс, активируя операцию ввода (действие, зависящее от применяемого устрой-
Х/Д ^ Входные данные недоступны
Рис. 10’10. Протокол синхронизации для устройства ввода.
ства ввода) и помещая логическое значение «ложно» на линию READY. По завершении операции ввода интерфейс сохраняет введенные данные в регистре DATA, который является в этом случае регистром с памятью в отличие от порта KBDATA в вышерассмотренном случае. В то же время он переводит разряд READY в состояние «истинно». Это означает, что процессор может осуществлять проверку разряда READY и считывать введенные данные, как только разряд READY примет значение
7б5 4107 6О
|kbcs Г
|| KBDATA
■60 (W)
I Данные 6 коде
Д5СП(К)
. Нвисполь-
зуется
OV (R)
duugUu U(Л )
■IEN (RW)
■RDV(R)
Рис. 10.11. Структура портов, используемая для программирования ввода с клавиатуры:
а — порт управления и синхронизации; б — порт ввода с клавиатуры.
«истинно». Введенные данные остаются неизменными до тех пор, пока не начнется следующая операция чтения.
В некоторых процессорах с изолированным вводом-выводом предусмотрены специальные команды для посылки управляющего импульса START I/O и проверки разряда READY. Например, в процессорах 21МХ фирмы Hewlett-Packard для этой цели служат команды STC рп и SFS рп, где ри — номер порта. Однако большинство микро-ЭВМ и многие мини-ЭВМ снабжены специальными портами управления и синхронизации, которые выполняют указанные функции, используя обычные команды ввода-вывода, независимо от способа реализации ввода-вывода — изолированного или с обращением к памяти.
В качестве примера на рис. 10.11,а показана структура порта управления и синхронизации KBCS, используемого при работе с клавиатурой. Он является одновременно портом ввода и портом вывода; доступ к разрядам может осуществляться одним из следующих способов:
Только чтение (R). Значение разряда устанавливается интерфейсом и может быть прочитано процессором; запись величины в разряд осуществить невозможно.
Только запись (W). Запись в разряд производится процессором; значение считываемой величины непредсказуемо.
Чтение!запись (RW). Можно осуществить как чтение значения разряда, так и запись в него процессором; при чтении обычно извлекается последнее записанное значение.
Не используется. Запись выполнить невозможно, значение считываемой величины непредсказуемо.
Каждый разряд порта выполняет определенную функцию управления и синхронизации. Запись 1 в разряд GO эквивалентна посылке импульса START I/O в интерфейс '(см- рис. 10.10). При этом производится сброс разряда RDY(READY) и активируется операция ввода. Для клавиатуры активирование операции состоит в сбросе разрядов порта KBDATA и ожидании, пока кто-нибудь не нажмет клавишу. Как только клавиша нажата, интерфейс осуществляет синхронизацию следующим образом: он помещает код ASCII, соответствующий клавише, в порт KBDATA и переводит разряд RDY в состояние 1. Например, следующая подпрограмма для системы 6809 читает символ с клавиатуры и помещает его в регистр А (подразумевается при этом, что символам KBCS и KBDATA присвоены значения адресов портов KBCS и KBDATA):
KBDIN
LDA
STA
*1
KBCS
Установить GO в 1
KBDWT
TST
KBCS
Активировать операцию ожидания, пока
BPL
KBDWT
RDY не установится в 1
LDA
KBDATA
Загрузка данных в регистр А
ANDA
RTS
#7FH
Сброс старшего разряда Возврат
Вышеприведенные команды ожидания перевода разряда RDY в состояние 1 называются циклом «занято-ожидание». Когда процессор находится в состоянии ожидания завершения операции ввода-вывода, никаких полезных вычислений не производится. Продолжительность состояния ожидания может быть уменьшена путем использования совмещенного ввода-вывода или прерываний.
В порте KBCS предусмотрены еще два разряда. Разряд IEN, установленный в 1, дает возможность осуществить прерывание процессора от клавиатуры, как будет описано в следующей главе. Он является разрядом чтения/записи, так что программа может легко определить возникновение прерывания от клавиатуры в любой момент времени (это используется в программах опроса прерываний, см. рис. 11.6). Разряд OV указывает на состояние «переполнения»: интерфейс устанавливает его в 1, когда с клавиатуры поступают два символа подряд без промежуточного чтения процессором порта KBDATA. Автоматический сброс этого разряда производится при установке в 1 разряда GO. На рис. 10.12 приводится программа, в которой используется бит OV для обнаружения переполнения. .
*
Keyboard input subroutine.
KBDIN
LDA
KBCS
Check for overrun.
ANDA
#20H
BNE
KBOUT
Exit with Z=0 on overrun.
LDA
#1
Else set GO to 1 to request a key.
STA
KBCS
KBDWT
TST
KBCS
Wait for RDY=1.
BPL
KBDWT
LDA
KBDATA
Get key data.
ANDA
#7FH
Clear MSB.
SETZ
Set Z:«1 for succesful return.
KBOUT
RTS
Return with 7-bit ASCII in A.
*
MAIN
•..
Initialization.
LOOP
Main processing loop.
JSR
KBDIN
Get the next character.
BNE
MISSIT
Bomb out if we missed it.
e e •
Otherwise process the character...
JMP
LOOP
...and go do some more.
MISSIT
...
Handle the error.
END
MAIN
Рис. 10.12. Программа чтения с клавиатуры с проверкой наличия переполнения.
Совмещенный ввод-вывод
Представим, что мы читаем символы с механического устройства ввода, такого, как устройство ввода с перфоленты. Из-за механического перемещения существует фиксированная задержка с момента времени, когда байт затребован, до того момента, когда он станет доступным. В предыдущем разделе для выполнения операции ввода мы использовали три основных шага: 1) запрос байта; 2) ожидание байта, 3) чтение байта. В подпрограмме ввода с перфоленты эти шаги выполняются следующим образом:
PTRIN
LDA
STA
*1
PTRCS
Установить GO в 1, активировать начало движения ленты
PTRWT
TST
PTRCS
Ожидание прекращения движения ленты
BPL
PTRWT
перед следующим байтом (RDY=1)
LDA
PTRDATA
Загрузка введенного байта в регистр А
RTS
Возврат
На рис. 10.13, а показано поведение во времени главной программы, которая вызывает приведенную здесь подпрограмму. Каждый раз, когда главная программа вызывает подпрограмму PTRIN, запрашивается очередной байт, и программа находится в состоянии ожидания в течение времени перемещения ленты.
Для уменьшения продолжительности или даже для исключения состояния ожидания можно совместить операции ввода- вывода с проведением вычислений. Ниже приводится подпрограмма ввода данных с перфоленты, в которой основные шаги
Выполнение main '
Выполнение
PTRIN
RBY
Выполнение
. HAIN ' Выполнение
PTRIN
RDY1
Рис. 10.13. Выполнение операций ввода-вывода: а — несовмещенных; б — совмещенных.
переупорядочены следующим образом: 1) ожидание очередного байта; 2) чтение очередного байта; 3) запрос следующего байта:
PTRIN TSTPTRIN TSTBPL LDA LDB STB RTSBPL LDA LDB STB RTSPTRCS PTRINPTRCS PTRINPTRDATA*1 PTRCSPTRDATA*1 PTRCSОжидание прекращения движения ленты перед очередным байтом (RDY=1)Загрузка введенного байта в регистр АУстановить GO в 1, активировать начало движения ленты ВозвратОжидание прекращения движения ленты перед очередным байтом (RDY=1)Загрузка введенного байта в регистр АУстановить GO в 1, активировать начало движения ленты Возврат
PTRIN TST
PTRIN TST
BPL LDA LDB STB RTS
BPL LDA LDB STB RTS
PTRCS PTRIN
PTRCS PTRIN
PTRDATA
*1 PTRCS
PTRDATA
*1 PTRCS
Ожидание прекращения движения ленты перед очередным байтом (RDY=1)
Загрузка введенного байта в регистр А
Установить GO в 1, активировать начало движения ленты Возврат
Ожидание прекращения движения ленты перед очередным байтом (RDY=1)
Загрузка введенного байта в регистр А
Установить GO в 1, активировать начало движения ленты Возврат
На рис. 10.13,6 показаны временные соотношения для новой подпрограммы. Когда подпрограмма PTRIN вызывается в первый раз, лента уже остановлена перед первым байтом, байт считывается, и лента начинает движение. Через некоторое время, когда подпрограмма PTRIN вызывается второй раз, лента продвинулась почти на половину расстояния до второго символа, и таким образом продолжительность состояния ожидания уменьшилась. Далее главная программа выполняет так много вычислений между вторым и третьим символами, что при третьем вызове подпрограммы PTRIN состояние ожидания вообще отсутствует. Таким образом, операция ввода-вывода (чтение следующего символа) всегда совмещается с вычислениями, которые должны выполняться перед тем, как главная программа вводит следующий символ.
Совмещенный ввод-вывод используется при работе с такими устройствами, в которых по запросу процессора перед каждой
Интерфейс ; v устройстваИнтерфейс ; v устройства8TARTI/08TARTI/0READYREADYЦПSTARTI/OЦПSTARTI/OREADYREADYDATADATAwzzzzzzzzawzszs^zz^wzzzzzzzzawzszs^zz^WRITEWRITEИстинноИстинноВремяВремяУД- Данные на шине недоступныУД- Данные на шине недоступныРис. 10.14. Протокол синхронизации для устройства вывода.Рис. 10.14. Протокол синхронизации для устройства вывода.DATADATAWRITEWRITE
Интерфейс ; v устройства
Интерфейс ; v устройства
8TARTI/0
8TARTI/0
READY
READY
ЦП
STARTI/O
ЦП
STARTI/O
READY
READY
DATA
DATA
wzzzzzzzzawzszs^zz^
wzzzzzzzzawzszs^zz^
WRITE
WRITE
Истинно
Истинно
Время
Время
УД- Данные на шине недоступны
УД- Данные на шине недоступны
Рис. 10.14. Протокол синхронизации для устройства вывода.
Рис. 10.14. Протокол синхронизации для устройства вывода.
DATA
DATA
WRITE
WRITE
операцией ввода-вывода должно выполняться механическое действие. Для того чтобы инициировать подпрограмму ввода- вывода, запрос на первую операцию ввода-вывода может быть сделан из главной программы.
Операции вывода
Протокол синхронизации для типичного устройства вывода показан на рис. 10.14. В начале процессор посылает данные, подлежащие выводу, в интерфейс, используя шину DATA и
управляющий сигнал WRITE, а интерфейс помещает данные во внутренний регистр. Затем процессор вырабатывает импульс START I/O, а интерфейс устанавливает разряд READY в со-
] 1 1 1 | | D5PCSII\DSPDATA
1— Q0(W) Не исполь- зуется ERR (R) IEN(RW) RDY (R)
|Данные в коЭе
ASCH (W) Не исполь- зуется
7 6 5 4
О
7 6
5
Рис. 10.15. Структура портов, используемая для программирования вывода символов:
а — порт управления и синхронизации; б — порт вывода.
стояние «ложно» и посылает данные в устройство вывода. Затем, как только могут быть приняты другие данные, разряд READY устанавливается в состояние «истинно».
Структура портов ввода-вывода для устройств вывода символов, таких, как печатающее устройство или дисплей на ЭЛТ, показана на рис. 10.15. Разряд RDY, установленный в 1, указывает, что устройство может принять следующий символ, в то время как разряд GO, указывает, что очередной символ из порта DSPDATA должен быть выведен в устройство вывода1). Разряд IEN разрешает произодить прерывания, а разряд ERR, установленный в 1, указывает на возникновение ошибки в устройстве при выводе предыдущего символа (например, в печатающем устройстве кончилась бумага). При установке в 1 разряда GO производится автоматический сброс разряда ERR.
Операции вывода почти всегда совмещены с вычислительными операциями. Например, если символ послан в устройство отображения, нерационально ждать, когда будет произведен его вывод. Программа вынуждена ждать только в тех случаях, когда ее операции выполняются настолько быстро, что она пытается вывести второй символ до того, как интерфейс обработал предыдущий. Данная ниже подпрограмма для системы 6809, использующая интерфейс, структура портов которого показана на рис. 10.15, может применяться для совмещенного вывода символов:
* Вывести на экран дисплея символ, находящийся в регистре А
CHROUTTSTDSPCS
BPLCHROUT
Предыдущий символ уже выведен?
Ожидание, пока не будет осуществлен вывод
STADSPDATA
LDA#1
STADSPCS
RTS
Пересылка нового символа в интерфейс... и его вывод
Возврат
ДРАЙВЕРЫ ВВОДА-ВЫВОДА
Большинство операционных систем ЭВМ обеспечены набором подпрограмм для выполнения операций ввода-вывода с использованием стандартных устройств ввода-вывода. Эти подпрограммы называют драйверами. Драйвер ввода-вывода «скрывает» от программиста технические детали устройства и его аппаратно-реализованного интерфейса, предоставляя в его распоряжение чисто программный интерфейс. В этом разделе мы описываем некоторые простые драйверы ввода-вывода как для того, чтобы продемонстрировать примеры составления таких
1} В некоторых интерфейсах разряд GO отсутствует. Каждый раз, когда процессор производит запись в порт DSPDATA интерфейс автоматически вырабатывает команду GO. .
программ, так и для того, чтобы показать типичный программный интерфейс, предоставляемый драйверами ввода-вывода программисту.
Драйверы ввода-вывода для клавиатуры и дисплея
Драйверы ввода и вывода для клавиатуры и дисплея показаны на рис. 10.16. Имеются две подпрограммы чтения символов с клавиатуры: подпрограмма CHRIN читает один символ и передает его в регистр А, подпрограмма LINEIN читает строку текста, оканчивающуюся символом возврата каретки, и помещает строку в буфер, который инициирует вызывающая программа. Предусмотрены также две подпрограммы для вывода символов на дисплей: подпрограмма CHROUT выводит один символ, переданный из регистра А, подпрограмма LINEOUT выводит на экран содержимое буфера, инициированного вызывающей программой.
Клавиатура и дисплей являются логически независимыми устройствами, даже если физически они размещены вместе, как в терминале с дисплеем. Поэтому символы, которые набираются на клавиатуре,, не выводятся автоматически на дисплей, пока они не будут обработаны программой вывода, как это предусмотрено в подпрограмме CHRIN.
Большинство устройств отображения и печати данных перемещают курсор или печатающий механизм к началу очередной строки, как только будет получен символ возврата каретки CR, но при этом не переводят курсор на новую строку. Чтобы избавить пользователя от заботы нажимать символ перевода строки (LF) после каждого возврата каретки, подпрограмма LINEIN автоматически посылает символ перевода строки на дисплей после получения символа CR. Подпрограмма LINEOUT также добавляет символ LF к каждому выводимому символу CR. Однако символ LF не следует автоматически за символом CR в подпрограммах CHRIN и CHROUT, поэтому по желанию пользователя символ CR может быть послан или получен без автоматического появления символа LF.
Главная программа, в которой используются драйверы LINEIN и LINEOUT, приведена на рис. 10.17. Программа считывает входную строку, переставляет символы в обратном порядке и выдает строку на дисплей. Псевдокоманда ассемблера FCC (Form Constant Characters) воспринимает строку символов как операнд и помещает значения символов в коде ASCII в последовательные байты памяти. Отметим, что форматы входных и выходных буферов совпадают, поэтому содержимое входного буфера, заполненного подпрограммой LINEIN, может быть выведено, если подпрограмме LINEOUT передан адрес второго слова буфера. Тщательно проанализируйте, какие указатели
* МОДУЛЬ ВВОДА-ВЫВОДА (КЛАВИАТУРА/ДИСПЛЕЙУ
* Этот модуль содержит подпрограммы CHRIN и LINEIN для чтение * одного символа или строки символов с клавиатуры. Он также * включает две подпрограммы CHROUT и LINEOUT для вывода од-
* него
*
символе
1 или строки
символов на дисплей
* Определение
управляющих
символов и адресов портов ввода
* вывода
LFEQU
ОАН
Line feed character.
CR
EQU
0DH
Carriage return character.-
KBCS
EQU
0FF00H
Keyboard control and status.
KBDATA
EQU
0FF01H
Keyboard data.
DSPCS
EQU
0FF02H
Display control and status.
DSPDATA
EQU
0FF03H
Display data.
*
* SUBROUTINE CHRIN — Read one input character from a keyboard
*and echo it on the display.
*'
* INPUTS, GLOBAL VARIABLES, LOCAL VARIABLES — None.
* OUTPUTS
* INCHAR — The character just read, passed in A.
*
* REGISTERS AFFECTED — A
CHRIN
LDA STA
#1
KBCS
Request one character,
WTIN
TST
KBCS
Is it ready?
BPL
WTIN
Wait for it.
LDA
KBDATA
Get the character.
ANDA
#7FH
Clear MSB.
JSR
CHROUT
Echo the character on the display.
RTS
Done, return.
*
* SUBROUTINE
CHROUT —
Print one character on a display.
*
* INPUTS
OUTCHAR — The character to be displayed, passed in A.
* OUTPUTS, GLOBAL VARIABLES, LOCAL VARIABLES — None. *
* REGISTERS AFFECTED — None.•
♦
CHROUT
TSTDSPCSHas previous characterbeendisplayed*
BPLCHROUTWaitforcompletion.
STADSPDATASendthenew charactertointerface.
PSHSASavethecharacter.
LDA#1Displaythecharacter.
STADSPCS“'
PULSARestorethecharacter.
RTSDone, return.
Рис. 10.16. Драйверы ввода-вывода для клавиатуры и дисплея.
* SUBROUTINE LINEIN - Считывание строки символов в буфер
* Эта подпрограмма считывает строку символов с терминального
* устройства в буфер, Формат которого приведен ниже. Входная
* строка оканчивается символом возврата каретки. Первые два * байта буфера устанавливаются а начальное состояние вызываю-' * щей программой для размещения адреса последнего байта буфе* ра. Это предохраняет от переполнения память, если пользователь * наберет слишком длинную строку. Следующие два байта буфера * резервируются для размещения переменной BUFPNT, в которой * хранится адрес последнего символа, введенного в буфер. Осталь- * ная часть буфера используется для размещения вводимых символов *
*
BUFFER FORMATBUFFER FORMATTXTBUFTXTBUFBUFPNT (TXTBUF+2)BUFPNT (TXTBUF+2)TXT3UF+4TXT3UF+4
BUFFER FORMAT
BUFFER FORMAT
TXTBUF
TXTBUF
BUFPNT (TXTBUF+2)
BUFPNT (TXTBUF+2)
TXT3UF+4
TXT3UF+4
* INPUTS — #TXTBUF — a pointer to TXTBUF, passed in X.
* OUTPUTS — TXTBUF+2,TXTBUF+3 — points to last character.
*TXTBUF+4,... — input characters.
* GLOBAL VARIABLES, LOCAL VARIABLES — None.
* REGISTERS AFFECTED — A, Y
* NOTES — TXTBUF,TXTBUF+1 are not disturbed by LINEIN and
* *
therefore may be initialized at load time.,
LINEIN
TFR
X,Y
Put address of first byte of
ADDY
#4
character buffer into Y.
GETCHR
JSR
CHRIN
Read one character.
STA
@Y
Store it.
CMPA
#CR
Done if carriage return,
BEQ
LINDUN
CMPY
@X
Else is it OK to bump pointer?
BHS
GETCHR
Not if at end of buffer.
ADDY
#1
Else bump pointer to next character.
BRA
GETCHR
Get another character.
LINDUN
STY
2 (X)
Save pointer to last character.
LDA
#lf
Give the typist a new line
JSR
CHROUT
(CHRIN has already echoed CR) .
RTS
Done, return.
* SUBROUTINE LINEOUT Вывод отрок» из буфера на дисплей
хЭта подпрограмма выдает строку символов на терминальное уст- * ройство из буфера, формат которого приведен ниже. Первые два * байта буфера содержат адрес последнего символа, который не- * обходимо вывести, за ними следует строка символов. Буфер мб- * жет содержать любое количество символов и строк
* BUFFER FORMAT
*TXTBUF Iо+•
*TXTBUF+2 I*I*I*I*TXTBUF+2 I*I*I*III
*TXTBUF+2 I
*I
*I
*I
*TXTBUF+2 I
*I
*I
*I
оII
оII
OII
К
*
* INPUTS — #TXTBUF — a pointer to TXTBUF, passed in register X * OUTPUTS, GLOBAL VARIABLES, LOCAL VARIABLES — None.
* REGISTERS AFFECTED — A, Y
* NOTES — Entire text buffer is not disturbed by LINEOUT
*and therefore may be initialized at load time.
*
LINEOUT TFR
X,Y
#1
Put address of first byte of character buffer into Y.
ADDY
PUTCHR
ADDY
#1
Point to next character.
LDA
@Y
Get it.
JSR
CHROUT
Display it.
CMPA
#CR
Character still in A —
BNE
NOTCR
was it a carriage return?
LDA
#LF
Always follow CR by a line feed.
JSR
CHROUT
NOTCR
CMPY
@X
At the end?
BLO
PUTCHR
Continue if not.
RTS
Else done, return*
Рис. 10.16. (Продолжение.)
буфера устанавливаются в начальное состояние во время загрузки при помощи псевдокоманд FCW и какие загружаются во время выполнения программы.
Драйверы ввода-вывода совместного использования
Очень часто к ЭВМ подключают несколько идентичных устройств, например терминалы с дисплеем. Обычно каждое устройство имеет свой интерфейс. Все интерфейсы идентичны, за исключением того, что каждый из них имеет свои номера портов, отличные от других. Вместо того чтобы составлять отдель-
*
Read
input lines and
display them in reverse order.
*
INBUF
FCW
INBUFE
Address of last byte in buffer
INBUFP
RMB
2
Reserve space for buffer pointer...
INBUFT
RMB
100
...and 100 characters of text.
INBUFE
EQU
*-1
Define address of last byte.
YTEMP
RMB
2
STACK
RMB
20
Reserve space for return-address stack
STACKE
EQU
*
MSG1
FCW
MSG1E
Address of last byte in message.
FCC
•Please type
an input line:1•
MSG1E
FCB
CR
Last byte is carriage return.
MSG2
FCW
MSG2E
Address of last byte in message.
FCC
•Your line in
reverse is as follows:’
MSG2E
FcB
CR
Last byte is carriage return.
REVERSE
LDS
#STACKE
Set up return-address stack.
LDX
#MSG1
Prompt user for an input line.
JSR
LINEOUT
LDX
#INBUF
Pass the address of input buffer.
JSR
LINEIN
Get a line of input.
LDX
#INBUFT
Address of first input character.
LDY
INBUFP
Address of last input character.
ADDY
#-1
Bump to char before carriage return.
SWAPEM
STY
YTEMP
Swap bytes until X > = Y.
CMPX
YTEMP
BHS
REVDUN
LDA
@x
Swap the bytes pointed to by X and Y.
LDB
@Y
STA
@Y
STB
@x
ADDX
#1
Bump X forward and Y backward.
ADDY
#-1
BRA
SWAPEM
Repeat.
REVDUN
LDX
#MSG2
Display the second message.
JSR
LINEOUT
LDX
#INBUFP
Display the reversed input line.
JSR
LINEOUT
JMP
REVERSE
Run the program forever.
END
REVERSE
Рис. 10.17. Главная программа, которая осуществляет вызов подпрограмм LINEIN и LINEOUT.Р
ные драйверы для каждого устройства, можно написать один драйвер ввода-вывода, который будет использоваться совместно всеми устройствами. Для этого необходимо, чтобы базовый адрес набора номеров портов устройства передавался драйверу в качестве параметра. Например, на рис. 10.18 показано, как модифицировать вышерассмотренные подпрограммы CHRIN и CHROUT, чтобы они работали с любой клавиатурой и дисплеем, у которых порты ввода-вывода имеют одинаковую относитель-
ную нумерацию и выполняют одни и те же функции. Адрес порта управления и состояния клавиатуры (KBCSADR) передается подпрограмме в регистре У, доступ к другим портам осуще-
* SUBROUTINE CHRINY —Read one input character from a selected
*keyboard and echo it on the display.
*
* INPUTS — KBCSADR — Address of the keyboard control and status
*port, passed in register Y.
* GLOBAL VARIABLES, LOCAL VARIABLES — None.
* OUTPUTS — INCHAR — The character just read, passed in A.
* REGISTERS AFFECTED — A
*
* Port addresses are defined relative to the address of KBCS.
KBCS
EQU
0
Keyboard control and status.
KBDATA
EQU
1
Keyboard data.
DSPCS
EQU
2
Display control and status.
DSPDATA
EQU
3
Display data.
CHRINY
LDA
#1
Request one character.
STA
KBCS (Y)
WTIN
TST
KBCS (Y)
Is it ready?
BPL
WTIN
Wait for it.
LDA
KBDATA (Y)
Get the character.
ANDA
I7FH
Clear MSB.
JSR
CHROUTY
Echo the character on the display.
*
RTS
Done, return.
* SUBROUTINE CHROUTY — Print one character on a selected display.
**r^
* INPUTS — KBCSADR* — Address of the keyboard control and status
*port, passed in register Y.
*OUTCHAR —? The character to be displayed, passed in A.
* OUTPUTS, GLOBAL VARIABLES, LOCAL VARIABLES — None.
* REGISTERS AFFECTED — None.
CHROUTY TST
DSPCS (Y)
BPL
CHROUTY
STA
DSPDATA (Y)
PSHS
A
LDA
#1
'STA
DSPCS (Y)
PULS
A
RTS
Has previous character been displayed? Wait for completion.
Send the new character to interface.
Save the character.
Display the character.
Restore the character.
Done, return.
Рис. 10.18. Драйверы совместного использования для ввода с клавиатуры и вывода на дисплей.
ствляется с использованием адресации по базе с фиксированным смещенным относительно содержимого регистра У.
Драйверы, показанные на рис. 10.18, являются более общими по сравнению с драйверами «индивидуального» назначения, рассмотренными выше. Действительно, подпрограмма CHROUT,
например, может быть представлена как CHROUTY с конкретным значением Y, определяемым следующим образомз
*Драйвервыводасимвола на терминал с номеромО
CHROUTO PSHSУХранение У
LDY #0FF00H Адрес KBCS для терминала О JSRCHROUTYВывод символа, содержащегося в А
PULSYВосстановлениеY
RTSВозврат
Драйверы совместного использования легче писать для ЭВМ, в которых реализован ввод-вывод с обращением к памяти, поскольку для динамического вычисления адреса порта во время выполнения программы приемлемы все способы косвенной адресации и адресации по базе. Что касается ЭВМ с изолированным вводом-выводом, то для некоторых из них невозможно написать драйверы совместного использования без применения самомоди- фицирующихся кодов. Например, в системах Intel 8080 и MCS-48 номер порта ввода-вывода всегда определяется как часть команды и нет команд, которые могли бы извлечь номер порта ввода-вывода из регистра. В системе Z8000 эта проблема решена путем введения двух модификаций команд ввода-вывода: одна определяет номер порта ввода-вывода как часть команды, в другой номер порта ввода-вывода извлекается из регистра.
УПРАЖНЕНИЯ
Разработайте структуру порта вывода для одноцифрового семиэлементного индикатора, интерфейс которого не имеет шифратора в отличие от схемы, приведенной на рис. 10.4,а. Используйте выходной порт SEGS, каждый разряд которого соответствует одному из семи элементов: 1 высвечивает элемент, а 0 гасит его. Изобразите схематически соответствие между разрядами SEGS и элементами индикатора. Затем напишите подпрограмму для системы 6809 или Z8000, в которой порт SEGS используется для вывода на дисплей одной двоично-десятичной цифры, переданной в регистр А или R0.
Представьте, что вам поручено проектирование микро-ЭВМ с устройством ввода, единственным назначением которого является отслеживание текущей даты и времени суток с точностью до миллисекунды. Вы должны так описать структуру порта ввода-вывода интерфейса устройства, чтобы инженеры могли начать конструирование, а программисты — приступить к составлению драйверов ввода-вывода. Все числа должны храниться как двоичные целые без знака. Порты ввода-вывода, связанные с устройством, должные обеспечить как установку начала отсчета момента времени, так и чтение информации о текущем времени.
Выполните предыдущее упражнение, используя двоично-десятичное представление для чисел там, где это целесообразно.
Программы ввода с клавиатуры и вывода на дисплей (рис. 10.6 и 10.8)) используют условные переходы по знаку для определения принадлежности вводимого символа допустимому диапазону. Хотя данные программы работают нормально, в общем случае для сравнения символов лучше использовать логические условные переходы. Объясните почему.
Покажите, как исключить одну команду СМР из подпрограммы RDDIG (рис. 10.6 и 10.8), выполнив вначале вычитание.
Докажите, что для подпрограммы CHRIN (рис. 10.12) существует небольшая вероятность того, что переполнение не будет обнаружено.
Перепишите подпрограмму CHRIN (рис. 10.12), используя совмещенный ввод-вывод. Покажите, гарантируется ли в этом случае возможность того, что переполнение всегда будет обнаружено? Увеличилась или уменьшилась вероятность переполнения по сравнению с первоначальной программой?
Перепишите подпрограмму RDDIG (рис. 10.6 или 10.8) таким образом, чтобы уменьшить продолжительность состояния ожидания, поместив команды ожидания освобождения клавиши в начало подпрограммы RDDIG. Объясните, в чем состоит отличие этих двух вариантов, если клавиши нажимают очень быстро.
Модифицируйте подпрограмму CHROUT (рис. 10.16) так, чтобы она обнаруживала ошибки, используя разряд ERR, приблизительно таким же способом, как в подпрограмме CHRIN (рис. 10.12).
Модифицируйте подпрограмму CHROUT (рис. 10.16) так, чтобы она работала с интерфейсом, у которого разряд GO отсутствует, как показано в примечании к разд. 10.3.3.
Большинство управляющих символов кода ASCII (колонки 0 и 1 в приложении А) не могут быть выведены на экран дисплея или напечатаны. Напишите драйвер CHROUT, который преобразует все управляющие символы, кроме CR, LF и BS, в последовательность из двух символов: символа «Л», за которым следует соответствующий символ из колонки 4 или 5 таблицы кодов ASCII. Это вполне правомерно, поскольку на большинстве клавиатур управляющие символы генерируются путем нажатия клавиши управления (CTRL) и печати соответствующего символа из колонки 4 или 5.
Напишите драйвер LINEIN, который допускает несложное редактирование строк, выведенных на экран терминала. Нажатие клавиши возврата на один шаг CTRL-H должно вызвать исключение введенного перед этим символа из буфера и соответствующее отображение в программе шага назад, шага вперед и опять шага назад, что приводит к исключению последнего символа из строки на экране и соответствующему перемещению курсора. Нажатие клавиши CTRL-C должно привести к исключению всех символов из буфера ввода и к началу новой строки на экране дисплея. ^Замечание. Нет необходимости физически удалять символы из буфера ввода, достаточно скорректировать соответствующим способом указатель буфера.) Позаботьтесь о том, чтобы ваша программа не допустила шага возврата за пределы адресного пространства буфера ввода.
Напишите драйвер LINEIN, допускающий несложное редактирование строк, введенных с клавиатуры и выводимых на устройство печати, например телетайп. Нажатие клавиши CTRL-C должно приводить к такому же результату, что и в предыдущем примере. Ввод символа исключения (нажатие клавиши DEL или RUB) должен приводить к исключению из буфера последнего введенного символа и выводу на печать символа «\» (обратный слеш) и исключаемого символа. Повторное нажатие DEL должно приводить к последовательному исключению ранее введенных символов из буфера и выводу их на печать. После вывода последнего исключенного символа в серии исключений драйвер должен вывести на печать второй символ «\». (Заметьте, что программа не знает, что она получила последний символ исключения до тех пор, пока не будет введен символ, отличный от символа исключения.) Поскольку чтение частично введенных строк со многими исключениями затруднительно, предусмотрите команду CTRL-R, которая печатает на устройстве печати новую строку, содержащую отредактированное содержание буфера ввода, и которая позволяет пользователю продолжить ввод со следующей позиции.
Напишите программу, которая объединяет драйверы ввода-вывода для клавиатуры и дисплея с модулем обработки очередей (см. гл. 9). Опишите четыре очереди: UCQ, LCQ, DQ и OQ, которыми управляет модуль обработ*
ки очередей. Предусмотрите главную программу, которая выполняет непрерывное чтение символов с клавиатуры с помощью подпрограммы CHRIN. Каждый считанный символ должен быть помещен в одну из очередей UCQ, LCQ, DQ или OQ в зависимости от того, является ли он соответственно прописной буквой, строчной буквой, десятичной цифрой или любым другим символом. Если очередь заполнена, символ должен быть сброшен. Полная очередь содержит 10 символов. Символ возврата каретки в очередь не ставится-: он означает вы борку из каждой очереди и вывод на дисплей не более шести символов. Содержимое каждой очереди передается на дисплей по отдельной линии.
Напишите несколько драйверов ввода-вывода для таймера, описанного в упр. 10.2. Вы должны определить формат буфера и написать две подпрограммы для установки значения текущего момента времени при считывании буфера и загрузке буфера. Позаботьтесь о том, как разрешить проблему корректного чтения текущего времени в тот момент, когда значение секунды изменяется от т.999 к п.000. В этом случае возможен ответ /и.000 или п.999 в зависимости от того, в каком порядке читаются порты. При каких других переходах может встретиться аномалия подобного рода? Как может решить эту проблему проектировщик интерфейса?
Напишите программу для модели «жизнь» описанной в упр. 7.5, так, чтобы размеры массива Life соответствовали размерам экрана терминала. Напишите подпрограмму DISPLAY, которая выводит текущее поколение из массива CURG на экран дисплея, вызывая другую подпрограмму GOUT, которая выполняет посимвольный вывод массива на экран. Если автоматический перевод строк отсутствует, то подпрограмма DISPLAY должна посылать символы CR и LF после каждой строки, за исключением последней. Если же автоматический перевод строк предусмотрен, подпрограмма DISPLAY не должна выдавать на экран последний символ последней строки, чтобы избежать перемещения строк по экрану. Напишите главную программу, которая настраивается на произвольные начальные условия Life, вычисляет очередное поколение и выводит его на экран.
ЛИТЕРАТУРА
Детальное описание систем ввода-вывода, с точки зрения проектировщика аппаратных средств, можно найти в таких книгах, как Hill, Peterson, Digital Systems: Hardware Organization and Design, 2nd ed., Wiley, 1978 и Peatman J., Microcomputer-Based Design, McGraw-Hill, 1977. ’
Технической реализации ввода-вывода микро-ЭВМ посвящена статья Дж. Уокерли [Wakerly J., Microprocessor Input/Output Architecture, Computer, 10, 2, 26—33 (Febr. 1977)]. •
В данной главе только затронуты вопросы организации драйверов ввода- вывода и буферных схем ввода-вывода. В одном из разделов книги Дж. Петерсона (Peterson J., Computer Organization and Assemly Language Programming, Academic Press, 1978) рассмотрено, как выполняется совмещенный ввод-вывод блоков данных. Подробное обсуждение нескольких буферных схем можно найти в книге Кнута (Knuth D. Е., Fundamental Algorithms, Addison-Wesley, 1973. [Имеется перевод: Кнут Д. Искусство программирования, т. 1, Основные алгоритмы. — М.: Мир, 1977]). В двух последних книгах примеры программ на языке ассемблера приведены для гипотетической ЭВМ MIX, разработанной Д. Кнутом.
IIIIОРГАНИЗАЦИЯ ПРЕРЫВАНИЙ И ПРЯМОГО ДОСТУПА К ПАМЯТИ
II
II
' «Прервем программу, чтобы сделать важное сообщение...»
Эта знакомая всем фраза определяет основную функцию системы прерываний: извещение программы о завершении операции ввода-вывода. Процессор не ожидает завершения операции ввода-вывода и осуществляет выполнение другой задачи. Программа обработки ввода-вывода выполняется процессором только после завершения операции ввода-вывода...
В разд. 11.1 и 11.2 этой главы описаны типичные структуры и программное обеспечение систем прерываний. В некоторых процессорах для сообщения о внешних событиях используются структуры, похожие на структуры прерываний (см. разд. 11.3).
Если применяются устройства, требующие большого количества операций ввода-вывода низкого уровня, для уменьшения загрузки процессора можно использовать канал прямого доступа к памяти (ПДП). При использовании канала ПДП процессор вырабатывает одну команду высокого уровня (например, «Чтение блока данных с ленты») для инициализации всей последовательности операции ввода-вывода более низкого уровня (например, чтение отдельных байтов).
В разд. 11.4 этой главы описано, каким образом в ПДП осуществляется операция передачи данных непосредственно между устройством ввода-вывода и памятью без участия процессора.
В разд. 11.5, 11.6 вводится понятие процессов ввода-вывода и описываются некоторые неожиданные ситуации, которые необходимо избегать, чтобы сохранить правильность и надежность программ при наличии прерываний.
ОСНОВНАЯ СТРУКТУРА СИСТЕМЫ ПРЕРЫВАНИЙ И ПРОГРАММИРОВАНИЕ
В простых системах ввода-вывода процессор может единственным способом определить, завершило ли устройство свою работу, последовательно проверяя флажок «Готово» (READY),
находящийся в интерфейсе устройства. Однако в ЭВМ, в состав которых входит много устройств ввода-вывода (УВВ), таких, как дисплеи, печатающие устройства и диски, для достижения наиболее эффективной работы все устройства должны функционировать параллельно. Недопустимо, чтобы процессор бесполезно тратил время, ожидая, пока какое-либо устройство не завершит свою работу, тогда как другое устройство уже готово к обслуживанию. Система прерываний решает эту проблему, позволяя любому УВВ запускать программу его обслуживания с помощью процессора после завершения операций ввода-вывода и освобождая процессор для выполнения других заданий, когда данное УВВ производит операции ввода-вывода.
Используя систему прерываний, каждое устройство по завершении операции ввода-вывода может послать в процессор сигнал запроса прерывания. Процессор воспринимает запрос на прерывание и временно прекращает выполнение текущей программы.
Затем процессор выполняет программу обработки прерывания для уже завершенной операции ввода-вывода, возможно вызывая при этом другую операцию ввода-вывода. После обслуживания прерывания процессор возвращает управление прерванной программе. Наличие прерываний никак не влияет на работу прерываемой программы, кроме того, что время ее выполнения возрастет. (Разве не хотелось бы вам, чтобы ваша любимая телевизионная передача после ее внезапного прерывания, вызванного какой-либо неисправностью, начиналась с того места, на котором вы закончили ее смотреть?)
Количество разновидностей структур систем прерываний по крайней мере такое же, как и число вариантов организации процессора1^. Рассмотрим некоторые основные положения и опишем достаточно простую структуру системы прерываний, используемой в семействе микропроцессоров 6809 фирмы Motorola.
Основные положения
Обычно прерывания допускаются только между командами, а не во время выполнения команды. Например, не разрешается прерывание команды LDA (X) + между загрузкой А и добавлением 1 к значению X. В программе модели на языке Паскаль для микропроцессора М6809 (рис. 5.4) наличие прерывания будет определяться процедурой CheckForInterrupt, вызываемой непосредственно после операции Execute в основном цикле программы:
о Возможно и больше, поскольку современные системы управления прерываниями на основе БИС позволяют проектировщику ЭВМ использовать мно- то различных структур прерываний независимо от процессора.
WHILE true DO {Цикл интерпретации команды Н6809)
BEGIN
Fetch;
Execute;
Check Forlnterrupt;
END;
При выполнении процедуры CheckForInterrupt процессор вовлекается в выполнение программы обработки прерываний, так что выбирается первая команда программы обработки прерывания.
Каждый процессор имеет средства полной блокировки прерываний. Обычно это один из разрядов состояния процессора, который либо устанавливается, либо сбрасывается. Если прерывания разрешены и имеют место, то выполняются следующие действия:
Информация о текущем состоянии процессора (или ее часть) запоминается и вводится программа обработки прерывания.
Производится идентификация устройства, от которого получен запрос на прерывание.
Распознается и обрабатывается условие, которое вызвало запрос интерфейса на прерывание.
Восстанавливается состояние процессора и возобновляется работа программы.
В первом из перечисленных шагов под «состоянием процессора» подразумевается состояние счетчика команд (PC) и всех регистров процессора. При наличии прерывания должно быть сохранено текущее значение счетчика команд (PC) для последующего возобновления прерванной программы. При этом сохраняется значение PC, являющееся адресом следующей команды, которая подлежала бы выполнению, если бы не произошло прерывания. Состояние всех регистров, которое может быть нарушено в программе, также должно быть сохранено и восстановлено в конце программы обработки прерывания. Минимальным условием является сохранение значений разрядов состояния, но обычно необходимо запоминать состояние всех регистров (без их использования программа обработки прерывания не может сделать сколько-нибудь значительной работы).
Процесс сохранения значения PC, регистров и разрядов состояния после появления прерывания и их последующее восстановление называется контекстным переключением. В системах с частыми прерываниями скорость таких переключений может оказывать существенное влияние на производительность работы этих систем. Поэтому во многих процессорах предусмотрены средства ускорения контекстного переключения, такие, как команды загрузки и хранения содержимого нескольких регист-
Адрес 1234
1235
1236
1237
1238
1239
123А
Память
LDB#81
LDA#
1236 |рс
STA
34
56
2345
2346
2347
2381
LDA
FF
Программа . обработки прорывания
EFF3 EFF4 EFF5 EFF6 EFF7 EFF8 EFF9 EFFA EFFB EFFC EFFD EFFE
" EFFF
RTI
2468
369С
ЗА21
EFFF
|08|СС
|FF| А
Область ।———। _ ► стековой81I
памяти|00| DPH
FFF8
FFF9
23
45
1 Вектор ^прерывания
а
Память
2345 |рс
EFF3 |s
|98|СС
| FF | А
|81|б
| 00 |dpr
2468|х
369С|Y
ЗА21lu
Рис. 11.1. Состояние программы во время обработки прерываний:
а — до приема запроса на прерывание; б — после приема запроса на прерывание.
ров в стек. Некоторые процессоры сохраняют и восстанавливают регистры автоматически при вызовах прерываний и возвратах из них.
Второй из указанных шагов — это идентификация устройства прерывания — может выполняться несколькими способами. При наличии только одного устройства прерывания второй шаг нс нужен.
Третий шаг — выявление и обработка условия, которое вызвало прерывание; выполняется с помощью обычных команд работы с портами ввода-вывода.
Последний шаг, возобновляющий прерванную программу, является еще одним контекстным переключением. Он часто выполняется с помощью специальной команды, которая восстанавливает прежнее состояние процессора; при этом осуществляется передача управления обратно в прерванную программу.
Простая система прерываний
Процессор 6809 фирмы Motorola имеет линию запроса прерывания IRQ. Интерфейс устройства может прервать работу процессора, подавая соответствующий логический сигнал на эту линию (или, как говорят схемотехники, «возбуждая» линию). Процессор примет запрос на прерывание по этой линии только в том случае, если разряд I (маска прерывания) регистра состояния СС установлен в 0 (разряд 4 на рис. 8.1). Таким образом, программа может запрещать, или «маскировать», прерывания, устанавливая разряд I в 1.
На практике оказывается, что несколько устройств может возбуждать IRQ. В таком случае процессор должен каким-то образом определять, которое из устройств сделало это. Эту ситуацию обсудим позже, а пока предположим, что к линии IRQ подсоединено только одно устройство.
Если процессор принимает запрос на прерывание, он автоматически выполняет всю последовательность действий. Как показано на рис. 11.1, полное состояние процессора сохраняется путем записи состояния PC и всех регистров, кроме регистра S, в стековую память (регистр S служит как указатель стека; в процессоре М6809 для этой цели используется регистр SP). Затем разряд I в регистре СС устанавливается в 1, что препятствует обработке последующих прерываний. Происходят также изменения разряда 7 регистра СС (см. разд. 11.1.4 и 11.1.5). Наконец, процессор загружает в PC 16-разрядное число, содержащееся в векторе прерывания и хранимое в ячейках с адресами FFF8H и FFF9H. Это число является начальным адресом программы обработки прерывания, которая была заранее составлена программистом. Даже если адрес вектора (FFF8H, FFF9H) аппаратно зафиксирован в процессоре, программа обработки прерывания может располагаться в произвольном месте памяти; при этом значение вектора прерывания определяет начальный адрес.
т После того как процессор принял сигнал прерывания, он возвращается к нормальной процедуре выборки и выполнения команд. Так как в PC был загружен начальный адрес программы
обработки прерывания, следующей будет выполняться первая команда этой программы. Упомянутая программа обрабатывает любое условие, вызвавшее это прерывание, и побуждает устройство снять запрос с линии IRQ. До освобождения линии IRQ запросы игнорировались, так как 1=1.
Программа обработки прерывания возвращается в прерванную программу с помощью команды RTI «Возврат из прерывания». Команда RTI восстанавливает первоначальное значение всех регистров (включая PC и СС), извлекая их значения из стека. Поэтому сразу после выполнения команды RTI программа будет иметь такое же состояние (рис. 11.1,а), как и в случае отсутствия прерывания (за исключением разряда 7 регистра СС).
Поскольку разряд I установлен в 0, при восстановлении состояния регистра СС, сохраненного в стеке, теперь могут обрабатываться другие прерывания, запрос на которые был послан по линии IRQ.
Программа ввода-вывода с использованием прерываний
На рис. 11.2 представлена программа для микропроцессора 6809, в которой для вывода строк текста на экран дисплея с использованием интерфейса дисплея, приведенного на рис. 10.15, применяется прерывание. Когда IEN = 1 и RDY=1, дисплей через интерфейс подает запрос прерывания на линию IRQ.
Таким образом, каждый раз после завершения операции вывода символа на экран на линию IRQ выдается запрос на прерывание. Этот запрос снимается с линии IRQ, когда программа STRINT посылает следующий символ на дисплей (устанавливая разряд GO, который сбрасывает разряд RDY) или когда программа STRINT определяет, что передача символов закончена (и сбрасывает разряд IEN).
Главная программа может выводить строку на дисплей, вызывая программу STROUT с адресом строки, хранимым в регистре X. Программа STROUT разрешает прерывание, осуществляет вывод первого символа и возврат управления в главную программу, которая может продолжать работу. Оставшаяся часть строки выводится на дисплей программой обработки прерывания STRINT, в которой для фиксации места выводимого символа используется переменная BUFPNT.
Временная диаграмма выполнения программы обработки прерывания изображена на рис. 11.3. Глобальная переменная SBUSY указывает, полностью ли выведена текущая строка, так что программа STROUT может автоматически обеспечить завершение вывода, если запрос следующей строки придет слишком рано (рис. 11.3,6). Заметим, что разряд RDY в регистре- 27—1393
* Подпрограммы STROUT и STRINT используются для вывода строк * текста на дисплей. По определению, строка должна быть после* довательностью ненулевых символов кода ASCII, оканчивающих* ся нулем.
* Для того чтобы вывести строку, программа вызывает подпро- * грамму STROUT, в которой регистр содержит адрес первого сим
* вола строки. Подпрограмма STROUT разрешает обработку преры- * вания дисплея, выводит первый символ и производит возврат в
* вызывающую программу. Последующие символы печатаются с по
* мощью подпрограммы обработки прерывания STRINT.
* Отличное от нуля значение глобальной переменной SBUSY ука- * зывает на то, что осуществляется последовательный вывод стро* ки. Главная программа должна произвести сброс SBUSY, так, что * подпрограмма STROUT появляется сразу же, как она была вызва-
* на. Когда подпрограмма STROUT вызвана, печать новой строки не * начнется до тех пор, пока не будет обнаружено, что предыдущая * строка полностью напечатана. STRINT производит сброс SBUSY * после печати последнего символа
ORG
0FFF8H
IRQ interrupt vector address.
FCW
STRINT
Address of interrupt service routine.
ORG
1000H
*
Define
display interface registers.
DSPCS
EQU
0FF02H
Display control and status port.
DSPDATA
EQU
0FF03H
Display data port.
*
Local
and global variables.
BUFPNT
RMW
1
Pointer to character being displayed.
SBUSY
RMB
1
Nonzero when string is being displayed
*
STROUT
TST
SBUSY
Is a previous string being displayed?
BNE
STROUT
Wait for it.
STX
BUFPNT
Save buffer pointer.
LDA
#0FFH
Set SBUSY flag.
STA
SBUSY
LDA
@X
Get first character.
STA
DSPDATA
Display it.
LDA
#41H
Set IEN and GO.
STA
DSPCS
RTS
Done, return.
*
*
Registers are already
saved on entry to STRINT.
STRINT
LDX
BUFPNT'
Get buffer pointer.
ADDX
#1
Bump pointer to next character.
STX
BUFPNT
LDA
@x
Get next character.
BEQ
STRDUN •
Quit if it's a zero.
STA
DSPDATA
Otherwise display it.
LDA
#41H
Set IEN and GO.
STA
DSPCS
RTI
Return to interrupted program.
STRDUN
CLRA
Redundant, since A=0 already.
STA
DSPCS
Clear IEN.
STA
SBUSY
Clear SBUSY flag.
RTI
Return to interrupted program.
*
Main program.
MAIM
...
Initialization.
CL RA STASBUSY
Clear SBUSY flag.
LOOP
. . .
Do some processing.
LDX#MSG1
JSRSTROUT
Print the first message.
Do some more processing.
LDX#MSG2
JSRSTROUT
Print the second message.
. ..
Do some more processing.
JMPLOOP
*
MSG1
FCC'The quick
FCB0
brown fox jumps over the lazy dog
MSG2
FCC'That was a
FCB0
pretty unimaginative message!'
*
ENDMAIN
Рис. 11.2. Подпрограмма вывода информации на дисплей с использованием прерываний.
ВыполнениеMAINВыполнениеSTROUTВыполнениеMAINВыполнениеSTROUTВыполнениеВыполнениеSTRINTSTRINTRDYRDYВыполнение MAINВыполнение, STROUTВыполнениеSTRINTВыполнение MAINВыполнение, STROUTВыполнениеSTRINTRDYRDY
ВыполнениеMAIN
ВыполнениеSTROUT
ВыполнениеMAIN
ВыполнениеSTROUT
Выполнение
Выполнение
STRINT
STRINT
RDY
RDY
Выполнение MAIN
Выполнение, STROUTВыполнениеSTRINT
Выполнение MAIN
Выполнение, STROUTВыполнениеSTRINT
RDY
RDY
u
Рис. 11.3. Временная диаграмма программы обработки прерывания:
а — вывод первой строки до получения запроса на вывод второй; б — запрос на вывод второй строки до окончания вывода первой.
27е
управления и состояния дисплея является аппаратным флажком, который указывает на завершение операции вывода одного символа, в то время как программный флажок SBUSY указывает на завершение программного процесса более высокого уровня, т. е. на вывод полной строки.
Линии запроса на прерывание
Кроме линии IRQ в микропроцессоре 6809 имеются еще две линии запроса на прерывание — NMI («Немаскируемое прерывание») и FIRQ («Быстрый запрос на прерывание»). Как следует из табл. 11.1, эти линии отличаются от IRQ адресами вектора прерывания, способом маскирования и регистрами, содержимое которых загружается в стек при приеме запроса на
Таблица 11.1. Запросы на прерывание и адреса векторов прерываний в процессоре 6809
Линия ввода
Адрес вектора
Разряд маски
Регистры, загружаемые в стек
IRQ
FFF8H
I
PC, и, Y, X, DPR, В, А,
«•
СС
FIRQ
FFF6H
F
PC, СС
NMI
FFFCH
Нет
PC, U, Y, X, DPR, В, А,
СС
RESET
FFFEH
Нет
Нет
Команда
Адрес вектора
Разряд маски
Регистры, загружаемые в стек
SWI
FFFAH
Нет
PC, и, Y, X, DPR, В, А,
СС
SWI2
FFF4H
Нет
PC, U, Y, X, DPR, В, А,
СС
SWI3
FFF2H
Нет
PC, U, Y, X, DPR, В, А,
СС
прерывание. На команды аппаратного сброса и программных прерываний процессор реагирует таким же образом, как и на команды аппаратных прерываний.
При поступлении запроса на прерывание по линии FIRQ в стек записывается только значение счетчика команд и разрядов регистра состояния; при этом уменьшается время выполнения программ обработки прерывания, в которых не используются многие либо вообще все регистры. Разряд Е («Полное состояние в стеке») в сохраняемом регистре СС содержит информацию о том, содержимое всех ли регистров было занесено в стек. С помощью команды RTI анализируют этот разряд и опреде*
ляют, содержимое каких регистров извлечь из стека. Во многих процессорах предусмотрены прерывания, аналогичные прерываниям по линии FIRQ, когда при приеме запроса на прерывание в стек загружается только значение счетчика команд и разрядов состояния. Содержимое всех остальных регистров, состояние которых может быть нарушено, должно сохраняться в стековой памяти с помощью команды PUSH в начале программы обработки прерывания и восстанавливаться с помощью команды POP по ее окончании.
Программа не может игнорировать запросы на прерывание, поступающие по линии NMI. Эта линия имеет особенно важное значение при использовании «сторожевого таймера», когда последний периодически прерывает программу. Даже если программа начинает «неистовствовать» и запрещает все другие прерывания, она не может помешать обработке прерывания, поступающего по линии NMI. Эта линия часто используется также при отключениях питания. Обычно источники питания выдают логический сигнал, показывающий, что было снято напряжение источника переменного напряжения и что выходное напряжение источника постоянного напряжения упало ниже допустимого уровня на какое-то время (несколько миллисекунд). Если этот сигнал воздействует на линию NMI, программа может осуществлять такие важные операции, как прекращение работы устройств ввода-вывода до восстановления питания.
Моделирование системы прерываний на языке Паскаль
На рис. 11.4 показана программная модель системы прерываний, написанная на языке Паскаль, которая дает представление о том, как микропроцессор 6809 проверяет и управляет запросами на прерывание. Процедура CheckForlnterrupt показывает, как проверяются запросы на прерывание после выполнения каждой команды. Процедура ReturnFromlnterrupt демонстрирует действия, выполняемые командой RTI.
Заметим, что состояние разряда Е в копии СС, размещенной в стеке, используется для определения того, какие регистры должны быть извлечены из стека.
ТИПЫ СИСТЕМ ПРЕРЫВАНИЙ
В этом разделе будет обсуждено несколько типов систем прерываний и программ, реализующих прерывания.
Уровни прерываний и разрешение прерываний
Процессор имеет многоуровневую систему прерываний, если у него есть много линий (или уровней), по которым устройства могут запрашивать прерывания, причем программа обработки прерывания одного уровня может быть прервана поступлением
PROGRAM Мб809 (input,output);
VAR MEM : ARRAY [0..65535] OF byte; {Main memory.} EAR, PC, X, Y, S, U : word; {Processor registers.} A, B, DPR : byte;
E, F, H, I, N, Z, V, C : bit; {CC register.}
IRQ, FIRQ, NMI : boolean; {External interrupt requests.}
PROCEDURE PushByte(b : byte);
BEGIN S := S - 1; MEM[S] : = b END;
PROCEDURE PushWord (w : word);
BEGIN PushByte(w[7:: 0]); PushByte (w[15::8]) END;
FUNCTION PopByte : byte;
r BEGIN PopByte := MEM[S]; S := S + 1 END;
. FUNCTION PopWord : word; {Pop two bytes to make*a word.) BEGIN PopWord := PopByte I PopByte END;
PROCEDURE.Acceptlnterrupt(vectorAddr : word)J BEGIN PushWord (PC) ; IF E =. 1 THEN BEGIN
PushWord (U); PushWord(Y); PushWord(X);
PushByte(DPR); PushByte(B); PushByte(A);
END;
PushByte(EIFIH111NIZIVIC);
PC[15::8] := MEM [vectorAddr]; PC[7::0] := MEM [vector Add r+1] ; END;
PROCEDURE CheckForlnterrupt; BEGIN IF NMI THEN
BEGIN E := 1; Acceptlnterrupt(OFFFCH) ; F := 1; I := 1 END
ELSE IF FIRQ AND (F=0) THEN '
BEGIN E := 0; Acceptlnterrupt(0FFF6H) ; F := 1; I := 1 END ELSE IF IRQ AND (1=0) THEN
BEGIN E := 1; Acceptlnterrupt(0FFF8H); I := 1 END; END;
PROCEDURE ReturnFromlnterrupt;
VAR CC : byte; BEGIN CC := PopByte; E := CC[7]; F := CC[6]; H := CC[5]; I := CC[4);
N := CC[3]; Z := CC[2]; V := CC[ 1 ] ; C := CC[0] ; IF E = 1 THEN
BEGIN A:=PopByte; B:=PopByte; DPR:=PopByte; X:=PopWord; Y:=PopWord; U:=PopWord END; PC := PopWord;
END;
Рис. 11.4. Программа на языке Паскаль обработки прерывания для микропроцессора 6809.
запроса на прерывание другого уровня. Если система прерываний имеет п уровней, то в любой момент времени могут «выполняться» до п программ обработки прерывания. Например, в микропроцессоре 6809 имеются три уровня прерывания (NMI, FIRQ и IRQ), в то время как в ЭВМ PDP-11 их четыре (BF4— BF7). Процессор MCS-48 располагает одноуровневой системой прерываний, при этом в любой момент времени может выполняться только одна программа обработки прерывания.
Многоуровневые системы прерываний полезны для объединения в группы устройств, имеющих близкие характеристики или приоритеты одного уровня. Например, всем дискам и накопителям на магнитной ленте в системе может быть присвоен приоритет одного уровня, а терминалам — другого. Тогда программа обработки прерывания терминала может быдъ прервана запросом от накопителя на магнитной ленте, нуждающегося в обслуживании.
Устанавливая или сбрасывая соответствующие разряды маски в слове состояния процессора, можно разрешать запрос на прерывание на каком-либо уровне, игнорируя все остальные запросы. Таким образом, программа может запрещать обслуживание всех прерываний от устройств с низким приоритетом (например, от терминалов) и разрешать обработку прерываний от устройств с высоким приоритетом (например, от дисков и накопителей на магнитной ленте).
Не считаясь с числом уровней прерываний, процессор может запретить поступление запроса на прерывание от какого-либо устройства с помощью разряда разрешения прерывания (IEN) в регистре управления и состояния интерфейса устройства. Эта
ситуация отображена на рис. 11.5 для микропроцессора 680£> (вентили И и ИЛИ были рассмотрены в разд. 1.2). К линии IRQ подсоединены выходы с сигналами запроса на прерывание трех интерфейсов. Однако только в двух интерфейсах (1 и 3) разряд IEN установлен в 1, так что они могут посылать запрос на прерывание, когда значение разряда RDY равно 1. В данный момент, как показано на рисунке, запрашивает прерывание только интерфейс 3. Если процессор маскирует запросы на прерывание по линии IRQ, установив разряд I в 1, то даже и этот запрос игнорируется (остается в состоянии ожидания) до тех пор, пока процессор не установит I в 0.
Вообще когда процессор допускает обслуживание прерывания на каком-либо уровне, он запрещает прерывания на этом, уровне до тех пор, пока не будет полностью обработано текущее прерывание.
Приоритеты прерываний
В многоуровневой системе прерываний приоритет прерывания определяет, может ли программа обработки прерывания одного уровня быть прервана запросом на прерывание другого уровня. Обычно в системах прерывания каждая линия прерывания имеет определенный приоритет. Например, линия NMI процессора 6809 имеет высший приоритет, за ней следуют линии FIRQ, IRQ; этот приоритет установлен аппаратными средствами с помощью разрядов маски I и F.
Возможна ситуация, когда два или большее число устройств одновременно направляют запросы на прерывание. Маловероятно, что это произойдет, когда два устройства завершат свои операции в один и тот же машинный цикл команды (~1 мкс). Скорее, два или более устройств посылают запросы на прерывание в течение длительного периода времени, когда процессор маскировал прерывания. Когда процессор, наконец, разрешит прерывание, много устройств, пославших запрос на прерывание^ будет ожидать обслуживания. Приоритет прерывания определяет порядок, в котором это прерывание будет обслужено. Обратимся опять к процессору 6809. Аппаратно реализованные приоритеты линий NMI, FIRQ, IRQ задают порядок, в котором эти линии проверяются после каждого цикла интерпретации команд (см. табл. 11.1).
В том случае, когда много интерфейсов производят запрос прерывания по одной и той же линии, процессор должен выбрать для обслуживания один из них. Этот выбор делается путем определения приоритетов интерфейсов. Обычно используется один из следующих двух способов:
Фиксированный приоритет. Интерфейсы проверяют в порядке установленных приоритетов, и обслуживается тот интерфейс, запрос которого обнаружен первым.
Круговой приоритет. Интерфейсы упорядочены по приоритетам, но поиск интерфейса, нуждающегося в обслуживании, начинается с интерфейса следующего (по приоритету) за только что обслуженным интерфейсом. Поиск интерфейса возвращается в начало списка интерфейсов по приоритету, когда достигнут последний интерфейс этого списка.
Круговой приоритет является более рациональным, чем фиксированный, так как удерживает быстрые устройства от монополизации обслуживания прерываний за счет медленных. Он также более надежен, так как предотвращает блокировку устройств с низким уровнем приоритета при неисправности устройств с высоким уровнем приоритета, проявляющейся в непрерывном запросе прерывания. Как мы увидим в следующем подразделе, круговой приоритет проще реализуется программно с помощью системы опроса.
Если прерывания реализуют аппаратным путем, то чаще используют фиксированный приоритет из-за менее сложных схем. Например, в системе прерываний PDP-11 приоритет каждого устройства определяется близостью его физического расположения по шине ввода-вывода к центральному процессору.
Распознавание запроса на прерывание путем опроса
* Если программа «знает», что какое-то устройство направило запрос на прерывание, но «не знает» какое именно, она должна просто «выйти и спросить»! Это и называется опросом.
Регистры управления и состояния большинства интерфейсов устройств содержат разряд или группу разрядов, которые показывают, был ли послан запрос на прерывание. Например, интерфейсы клавиатуры и дисплея (рис. 10.11 и 10.15) запрашивают подпрограмму обработки прерывания, когда значения разрядов RDY и IEN находятся в I. Если оба устройства могут послать запрос на прерывание, используя общую линию IRQ, программа может определить в любой момент времени, какое устройство нуждается в обслуживании, проверив значение разрядов RDY и IEN в обоих интерфейсах.
Если делается одновременно множество запросов на прерывание, порядок опроса интерфейсов определяется их приоритетом. На рис. 11.6 приведена программа для процессора 6809, содержащая подпрограммы опроса при фиксированном и круговом приоритетах. „Таблица опроса" определяет адрес регистра управления и состояния, а также начальный адрес программы обработки прерывания для каждого устройства, которое может направлять запрос на прерывание по линии IRQ. Этот пример предполагает существование только трех устройств, но может быть обслужено произвольное число устройств, если использовать дополнительные входы в таблицу.
* Подпрограммы FPOLL и RF1LL распознают устройство прорыв- * ния путем опроса и передачи управления программе обработки
* прерывания. В подпрограмме FPOLL используется опрос по фик- * сированному приоритету, а в подпрограмме RPOLL-по кругово- * му приоритету. Полагается,что работа устройства может быть * прервана, если разряды RDY и IEN регистра управления и * состояния находятся в 1.
* В таблицу опроса включено одно или несколько устройств
* прерывания. Вход в каждую таблицу содержит 4 байт (два
* слова)-адрес регистра управления и состояния устройства и
* начальный адрес его программ!! обработки прерывания. Таблица * оканчивается нулевым словом
ORG0FFF8H
IRQ interrupt vector address. Fixed priority polling
FCWFPOLL
*
ORG1000Н
(or use RPOLL for round-robin)
*
Polling table. Addresses of control and status registers
*
and service routines
are assumed to be defined elsewhere.
POLLTB
FCWKBDCS,KBDINT
FCWDSPCS,DSPINT
FCWPTRCS,PTRINT
FCW0
Keyboard.
Display.
Papertape reader.
End of table.
LASTP *
RMW1
Last polled (used by RPOLL only) ..• ...initialize to #POLLTB at run time.
*
Fixed priority polling routine.
FPOLL
LDX#POLLTB-4
Start at beginning of POLLTB.
FNEXT
ADDX#4
ldyex
BEQERROR
LDA@Y
ANDA#0C0H
CMPA#0C0H
BNEFNEXT
LDX2 (X)
jmpex
Address of next item in POLLTB.
Get address of C&S reg for next device.
False interrupt if at end of table.
Check C&S reg — bits 7 and 6=1?
Clear 6 low-order bits.
A is 11000000 if device is interrupting Try next device if not.
Else get address of service routine... ...and jump to it.
*
Round-robin polling
routine.
RPOLL
LDXLASTP
Start after last device polled.
RNEXT
ADDX#4
ldyex
BNERCHK
LDX#POLLTB
LDYex
Address of next item in POLLTB.
Get address of C&S reg for next device.
Skip if nonzero.
Else wrap around to start of table.
Get address of C&S reg for first device
RCHK
LDAeY
ANDA#0C0H
CMPA#0C0H
BNECHKLST
STXLASTP
LDX2 (X)
jmpex
Check C&S reg — bits 7 and 6=1?
Clear .6 low-order bits.
A is 11000000 if device is interrupting Try next device if not.
Else save pointer to last polled... get address of service routine... ...and jump to it.
CHKLST
CMPX LASTP
BNERNEXT
Checked all devices?
No, try another.
ERROR
RTI
False interrupt, return.
Рис. 11.6. Подпрограммы опроса запросов на прерывание для микропроцессора 6809.
В программе опроса FPOLL, в которой в определенном порядке осуществляется проверка регистров управления и состояния, реализован фиксированный приоритет. В программе RPOLL для запоминания последнего опрошенного интерфейса используется переменная LASTP и осуществляется круговой приоритет. В зависимости от того, какой адрес используется программистом для инициализации вектора IRQ, запрос на прерывание, посланный по линии IRQ, обслуживается либо программой FPOLL, либо программой RPOLL (в данном примере программой FPOLL).
Прерывания по вектору
В системах с частыми прерываниями, содержащих много устройств, время, затрачиваемое на выполнение программы опроса, может быть значительным. Поэтому многие ЭВМ располагают аппаратными средствами, с помощью которых производится автоматический выбор программы обработки прерывания для каждого устройства. Система прерываний с такими возможностями называется системой с прерыванием по вектору.
Для реализации прерываний по вектору требуется аппаратно-реализованная система приоритетов (фиксированная или круговая). Если процессор принимает запрос на прерывание, он посылает интерфейсу соответствующего устройства с высшим приоритетом сигнал подтверждения приема. По получении этого сигнала интерфейс посылает свой вектор прерывания в процессор.
Способы реализации системы с прерыванием по вектору меняются в разных ЭВМ в широких пределах. Можно сказать, что процессор 6809 имеет векторные прерывания для трех устройств, каждое из которых соединено с одной из трех линий запросов на прерывание. Однако термин «прерывание по вектору» обычно применяется для процессоров с большим числом векторов прерывания.
Используя дополнительное оборудование, можно реализовать векторы прерывания для многих устройств, подсоединенных к одной из линий запроса на прерывание процессора 6809, например к линии IRQ. Это оборудование может обеспечить выбор приоритетов устройств, а также возможность отклика на операции чтения по шинам памяти и ввода-вывода по адресам FFF8H и FFF9H. В этих ячейках «псевдопамяти» может содержаться текущий адрес программы обработки прерывания устройства с наивысшим приоритетом, нуждающегося в обслуживании. Вместо программиста, задающего адрес программы обработки прерывания во время загрузки, в данном случае этот адрес определяется динамически аппаратным путем во время
прогона в соответствии с тем, какое устройство нуждается в обслуживании.
Архитектура процессора PDP-11 имеет сложные аппаратные средства прерывания по вектору. Первые 128 слов памяти резервируются для размещения таблицы, содержащей 64 вектора прерывания, состоящих из двух слов. Интерфейс устройства, нуждающегося в обслуживании, идентифицирует себя, посылая соответствующий указатель. Выбранная строка таблицы содержит новые значения PC и PSW, которые используются процессором после того, как он поместит старые значения в стек. Обычно программист производит инициализацию записей таблицы, содержащих PC и PSW, для каждой программы обработки прерывания. Микро-ЭВМ 68 000, Z8000, 9900 и 8086 имеют схожую систему с прерыванием по вектору.
СПЕЦИАЛЬНЫЕ И ПРОГРАММНЫЕ ПРЕРЫВАНИЯ
Специальные прерывания
Некоторые процессы могут аппаратным путем формировать запросы на прерывание при появлении определенных условий. Такие прерывания часто называют специальными прерываниями, чтобы отличать их от прерываний устройств ввода-вывода. Они могут иметь место при наличии следующих ситуаций:
при исключительных условиях, таких, как переполнение или антипереполнение при выполнении операций с плавающей точкой;
при программных сбоях, т. е. если предпринимаются попытки выполнить неверную или неопределенную команду или осуществить обращение к несуществующей или защищенной памяти;
при аппаратных сбоях, которые могут быть вызваны отключением источника питания или ошибками, обнаруженными при контроле на четность.
Если происходит специальное прерывание, то процессор записывает содержимое PSW и PC в стек точно так же, как он делает это при прерывании, а затем переходит к выполнению программы обслуживания, начальный адрес которой содержится в заранее определенной ячейке памяти (векторе специального прерывания). Обычно в процессорах различные условия или группы условий имеют разные векторы специального прерывания. Микро-ЭВМ Z8000, кроме того, посылает 16-разрядный идентификатор в стек, который является условием для специального прерывания.
Программные прерывания
Во многих процессорах имеются команды, воздействие которых на состояние процессора схоже с аппаратными прерываниями. С помощью этих команд производят программные прерывания или системные вызовы.
Например, в микропроцессоре 6809 предусмотрены три 1- байтовые команды SWI, SWI2 и SWI3. При выполнении каждой из них содержимое регистров процессора загружается в стек, подобно тому как это делается при аппаратных прерываниях по линиям IRQ или NMI. Далее управление передается соответствующей программе обработки прерывания, 16-разряд- ный начальный адрес которой определяется как адрес вектора прерывания (табл. 11.1). Например, начальный адрес программы обработки прерывания SWI содержится в байтах памяти по адресу FFFAH и FFFBH. Программа обработки прерывания выполняет некоторые действия, а затем возвращает управление вызвавшей программе с помощью команды RTI.
Команды программных прерываний часто используют как удобное и эффективное средство вызова вспомогательных программ — утилит — операционной системы. Эти команды эффективны вследствие того, что обычно короче команд переходов к подпрограммам, а удобство их использования связано с тем, что в этом случае в вызывающей программе не требуется указания адреса операционной системы в памяти во время трансляции, загрузки или даже выполнения программы.
В системах с преобразованием адресов памяти программные прерывания являются обычно единственным средством вызова операционной системы в программе, так как прямой вызов подпрограммы по адресу в области памяти, зарезервированной для операционной системы, обычно фиксируется как нарушение защиты.
Хотя в микропроцессоре 6809 существует только несколько команд программных прерываний, можно вызвать большее число различных вспомогательных программ операционной системы, помещая 1-байтовый код после команды SWI. Когда программа обработки прерывания запущена, значение PC, загруженное в стек, указывает на этот байт. В программе обработки прерывания этот байт может использоваться в качестве индекса (адресной константы) в своей таблице адресов вспомогательных программ, эта же программа может увеличить помещенное в стек значение PC, так что при возврате из программы обработки прерывания 1-байтовый код будет пропущен.
В микропроцессоре Z8000 команда системного вызова SC имеет длину 16 бит. Она содержит 8 бит кода операции и 8-битовое поле операнда, в котором пользователь может разместить произвольную величину.
При выполнении программной команды SC процессор передает в стек адрес возврата, содержимое регистра состояния процессора и собственно команду SC и переходит к выполнению программы обработки прерывания, начальный адрес которой в памяти указывает вектор прерывания в памяти, так же как в микропроцессоре 6809. Во время выполнения программы обработки прерывания может быть считан второй байт команды SC, загруженной в стек, и его можно использовать в качестве индекса в таблице адресов вспомогательных программ.
ПРЯМОЙ ДОСТУП К ПАМЯТИ
Необходимость использования прямого доступа к памяти
Для сравнительно медленных устройств, таких, как дисплеи и печатающие устройства, имеет смысл осуществлять прерывание программы для каждой передаваемой величины. Предположим, что для выполнения программы обработки прерывания устройства, включая сохранение содержимого регистров и их восстановление, обслуживание устройств и операции с буферами ввода-вывода, требуется 100 команд, или 100 мкс. Тогда при скорости передачи 100 пересылок (прерываний) в секунду используется только 1% времени, отведенного процессору для выполнения команд, так что у процессора остается много времени для решения других задач.
Рассмотрим выполнение операций с устройствами памяти ‘большой емкости, например дисками и накопителями на магнитной ленте. Информация, хранимая в этих устройствах, организована в виде блоков размером от 128 до 2048 или более байт. Размер блока определяется форматом данных конкретных устройств, а блок является основной единицей передачи информации. Для того чтобы осуществить программным способом пе- передачу одного блока данных между устройством памяти большой емкости и основной памятью, программа должна обеспечить выполнение такой механической операции, как перемотка ленты, чтобы подвести нужный блок под головку чтения/записи. Время, затрачиваемое процессором на ожидание завершения этой операции, называется временем ожидания доступа. Среднее время ожидания доступа при работе с дисками составляет от 8 до 250 мс. При использовани накопителей на магнитной ленте это время может исчисляться минутами. В любом случае для того, чтобы избежать длительного времени ожидания процессора до сигнализации о появлении выбранного блока под головкой чтения/записи, целесообразно использование прерывания.
При установке выбранного на ленте блока под головкой чте- ния/записи включается прерывание. Например, для 125-мм гибкого диска с ординарной плотностью записи время записи/ /считывания 1 байт составляет 64 мкс. Маловероятно, что программа обработки прерывания может за такое время выполнить все требуемые операции и вернуться к прерванной программе до появления следующего байта. Очевидно, что для выполнения передачи требуется эффективный программный цикл «занято- ожидание».
Проблема программирования передачи данных для гибких дисков с удвоенной плотностью записи еще более сложная, так как новый байт появляется через 32 мкс. Передача данных программным способом для обычных «жестких» дисков невозможна для большинства процессоров, так как следующий байт появляется через каждые 8 мкс или даже меньше. Поэтому для решения этой задачи следует искать другой подход.
Каналом прямого доступа к памяти (ПДП) называют средства, позволяющие осуществлять быстрый обмен данными непосредственно между основной памятью и интерфейсом устройства ввода-вывода без участия процессора. Обычно шиной памяти управляет центральный процессор и обеспечивает для каждой передачи данных адреса и управляющие сигналы. Однако канал ПДП может временно взять на себя функции управления и осуществлять передачу данных непосредственно между устройством и основной памятью.
Предположим, что жесткий диск передает 1 байт за 8 мкс, а время цикла основной памяти составляет 1 мкс. В этом случае канал ПДП может взять один цикл памяти из каждых восьми для передачи данных диска, пропуская 7 из каждых 8 циклов памяти для работы центрального процессора.
Так как канал ПДП осуществляет передачу данных непосредственно между устройством и памятью, он не влияет на состояние центрального процессора (предполагается, что при использовании ПДП не производится записи информации в память, отведенную для текущей программы или ее данных). Единственным действием, оказывающим влияние на работу программы, является то, что команды выполняются иногда за большее время, так как происходят задержки при обращении к памяти.
В ранее приведенном примере с диском работа процессора замедляется максимально на 12,5%. В действительности потери времени могут быть еще меньше, так как ПДП может иногда занимать циклы, которые не используются центральным процессором. Например, с помощью канала ПДП можно передавать данные в то время, когда процессор выполняет команды умножения, деления или другие команды, на скорость выполнения; которых время обращения к памяти не влияет.
Программирование работы канала ПДП
Как и для других интерфейсов ввода-вывода, средства, используемые для программирования работы каналов ПДП, определяются типом устройств ввода-вывода и моделью ЭВМ. Однако можно отметить некоторые общие требования для всех каналов ПДП устройств с памятью большой емкости, таких, как
Адрес 7 5 5 4 3 2 10Mill . In^Адрес 7 5 5 4 3 2 10Mill . In^RDY(R)JEN (RW)RDY(R)JEN (RW)ERR (R)ERR (R)GO WR/W (RW)GO WR/W (RW)-He используются-He используютсяFF41Адрес памятиDMADDRFF42-(RW)-FF43Номер , блока дискаDBLOCKFF40”(RW)-FF45Счетчик байтовDBYCNTFF46~(RW)FF41Адрес памятиDMADDRFF42-(RW)-FF43Номер , блока дискаDBLOCKFF40”(RW)-FF45Счетчик байтовDBYCNTFF46~(RW)
Адрес 7 5 5 4 3 2 10
Mill . In^
Адрес 7 5 5 4 3 2 10
Mill . In^
RDY(R)
JEN (RW)
RDY(R)
JEN (RW)
ERR (R)
ERR (R)
GO W
R/W (RW)
GO W
R/W (RW)
-He используются
-He используются
FF41
Адрес памяти
DMADDR
FF42
-(RW)-
FF43
Номер , блока диска
DBLOCK
FF40
”(RW)-
FF45
Счетчик байтов
DBYCNT
FF46
~(RW)
FF41
Адрес памяти
DMADDR
FF42
-(RW)-
FF43
Номер , блока диска
DBLOCK
FF40
”(RW)-
FF45
Счетчик байтов
DBYCNT
FF46
~(RW)
Рис. 11.7. Средства, используемые для составления программы работы канала ПДП с диском.
диски. Для того чтобы осуществить передачу, каналу должно быть сообщено, откуда взять данные, куда поместить их на диске или в основной памяти и сколько байтов передавать. Кроме того, так как диск является устройством, позволяющим осуществлять как запись, так и чтение данных, каналу ПДП должно быть указано направление передачи: из диска в память (чтение) или из памяти в диск (запись).
Средства, используемые для составления программы работы канала ПДП с диском микропроцессора 6809, показаны на рис. 11.7. Регистр управления и состояния (DCS) содержит обычные для других интерфейсов устройств разряды: RDY, IEN, ERR, GO. Кроме того, он включает разряд R/W для выбора направления передачи (1—чтение, 0 — запись). При IEN = 1 канал ПДП вызывает прерывание, когда завершается передача блока, а не байта.
С помощью 16-разрядного регистра DMADDR задается начальный адрес доступа к основной памяти. Другой 16-разряд- ный регистр DBLOCK указывает для передачи начальный номер блока диска. В зависимости от типа диска регистр DBLOCK может быть разделен на поля, которые содержат информацию о номерах дисковода, диска, дорожки и сектора. Для больших дисков может потребоваться несколько таких регистров. Во всяком случае, с помощью регистра DBLOCK осуществляется адресация только полных блоков диска, а не отдельных байтов. Размеры блока определяются типом диска (в этом примере будем использовать блоки размером 256 байт). .
В регистре DBYCNT содержится информация о числе байтов, которые должны передаваться между диском и основной па-
мятью. Обычно в этом регистре устанавливается число байтов, равное размеру блока, так что передается один блок. Если регистр DBYCNT определяет число байтов, большее чем их содержится в блоке, то канал ПДП автоматически увеличит на единицу номер блока в регистре DBLOCK для выбора последую-
BLKSIZ
EQU
256
Disk block size.
*
Addresses of disk interface registers.
DCS
EQU
0FF40H
Control and status.
DMADDR
EQU
0FF41H
Main memory address.
DBLOCK
EQU
0FF43H
Block number.
DBYCNT
EQU
0FF45H
Number of bytes to transfer.
*
BLOCKN
RMW
1
Program variable — disk block #.
DSKBUF
RMB
BLKSIZ
Buffer for one disk block.
*
DISKIO
Begin program.
LOOP
Process one block per iteration.
*
Determine block number, put in BLOCKN.
*
LDX
#DSKBUF
Now ready to get a block from the disk. Address of memory buffer for transfer.
STX
DMADDR
Store in disk memory address register.
LDX
BLOCKN
Disk block number.
STX
DBLOCK
Store in disk block number register.
LDX
#BLKSIZ
Block and buffer size.
STX
DBYCNT
Store in disk byte count register.
LDA
#03H
Set disk for read and go.
STA
DCS
Now the operation has begun.
*
If possible, do
other work while waiting.
*
Now we
need the
buffer contents — has the reading finished?
WTDSK
LDA
DCS
BPL
WTDSK
Wait until ready.
ANDA
#20H
Check error bit.
BNE
DERROR
Handle errors if set.
*
Now process the buffer...
JMP
LOOP
...and go do some more.
DERROR
... (
Handle disk errors.
Рис. 11.8. Программа ввода данных с диска с использованием ПДП.
щего блока. Запись или считывание информации на диски или накопители на магнитной ленте физически осуществляется только целыми блоками. Если число байтов, указанное в регистре DBYCNT, не кратно размеру блока, то канал ПДП должен компенсировать это. Например, при операции чтения канал ПДП осуществляет чтение последнего блока из устройства, но в основную память записываются только байты, определенные регистром DBYCNT. При операции записи с помощью канала ПДП
осуществляется заполнение конца последнего блока нулями для достижения размера блока требуемой величины.
Программа, используемая ПДП для чтения блоков данных с диска, показана на рис. 11.8. С помощью этой программы производится чтение одного блока с диска с использованием ПДП; каждый блок должен быть размещен в 256-байтовом буфере памяти DSKBUF. После выдачи запроса на чтение блока продолжается выполнение программы, и когда программа готова к обработке блока, она должна выработать режим «заня- то-ожидание», установив соответствующим образом разряд RDY, в регистре DCS. После обработки блока программа решает, какой следующий блок подлежит обработке, и действия повторяются.
При выполнении программы чтение второго блока с диска не может быть осуществлено, пока не будет обработан первый блок, так как при передаче посредством ПДП в DSKBUF может быть перезаписано содержимое второго блока до того, как программа закончила обработку первого блока. Однако применение двойного буферирования делает возможным такое совмещение. При этом предусматриваются два буфера: с помощью ПДП заполняется буфер ввода-вывода, в то время как программа осуществляет выбор данных из рабочего буфера. После завершения каждой итерации программы и передачи посредством ПДП программа осуществляет передачу содержимого буфера ввода-вывода в рабочий буфер для реализации следующей итерации и инициирует другую передачу. В другом варианте операция перезаписывания может быть исключена путем изменения функций двух буферов после каждой итерации. В любом случае, если вычисления производятся быстрее, чем операции ввода-вывода, устройство ввода-вывода непрерывно занято.
В многоблочных передачах для завершения операции ПДП часто вместо режима «занято-ожидание» используются прерывания. Прерывание указывает на завершение передачи блока в режиме ПДП, а программа обработки прерывания инициирует передачу следующего блока в режиме ПДП. По окончании передачи последнего блока программа обработки прерывания подает соответствующий сигнал в главную программу, используя программный флажок. Аналогичная операция осуществляется в программе управления устройством построчного вывода на дисплей (рис. 11.2), за исключением того, что прерывание происходит один раз после передачи блока, а не символа.
Дисплеи
Во многих ЭВМ одно из основных применений ПДП связано с работой дисплеев. В этом устройстве канал ПДП используется для обращения к блоку памяти, в которой каждый байт со-
ответствует положению символа на экране дисплея. Для того чтобы послать символ в соответствующее место экрана, программным путем значение символа записывается в коде ASCII в соответствующую ячейку буфера памяти. Канал ПДП автоматически выбирает каждый байт из буфера с частотой 60 Гц и посылает каждый символ в видеоцепь, которая изменяет изображение на экране дисплея. В качестве последнего обычно используется видеомонитор с высокой разрешающей способностью. Если высокая разрешающая способность не нужна, то вместо монитора можно применять обычный телевизионный приемник.
В машинной графике используются растровые дисплеи. В этом случае каждый бит каждого байта буфера экрана соответствует точке на экране (или элементу изображения), которая может быть «включена» или «выключена», т. е. светлой или темной. Изображение на экране формируется программным способом путем установки соответствующих битов в буфере экрана. Заметим, что для дисплеев с высокой разрешающей способностью требуются буферы экрана большей емкости, чем для дисплеев с низкой разрешающей способностью. Например, для дисплея 512x512 элементов необходимы 218 бит, или 32К байт памяти. Для реализации дополнительных возможностей, таких, как полутона и цвет каждого элемента изображения на экране, требуется более 1 бит информации, и объем памяти увеличивается в соответствующее число раз.
Дисплеи имеют особенности, определяемые их применением. Например, адреса буфера экрана могут быть зафиксированы аппаратным путем или пользователь может выбрать один из двух или более буферов, посылая соответствующие команды ввода-вывода в интерфейс ПДП. В других случаях для дисплеев может потребоваться программа для перемещения данных в буфере, чтобы освободить место для новой строки в конце, или может требоваться только указатель одной строки, которая должна быть модифицирована. Во втором случае ПДП запускает дисплей с середины буфера в ячейке, соответствующей верхней строке экрана; по достижении конца буфера происходит переход в начало буфера.
К мини-ЭВМ с дисплеями относятся Apple II, Apple III и TRS-80. Дисплеи вместе с встроенной клавиатурой применяются вместо терминалов с дисплеем, которые можно также использовать для связи с ЭВМ. Многие терминалы с дисплеем, такие, как Heath/Zenith Hl9, содержат микро-ЭВМ с интерфейсом дисплея.
ПРОЦЕССЫ ПРЕРЫВАНИЙ
Абстрактное понятие «процесс» удобно использовать для анализа поведения программ при возникновении прерываний.
Процессы
Нестрого процесс можно определить как выполнение программы. Состояние процесса учитывает состояние центрального процессора и состояние переменных программы, записанных в памяти. Таким образом, можно сказать, что процесс — это дискретное развитие во времени различимых изменяющихся состояний. Минимальным отрезком времени является цикл интерпретации команды процессора.
При наличии прерываний программу удобно рассматривать как набор процессов, которые активируются, временно приостанавливаются, возобновляются и заканчиваются на протяжении выполнения программы. Процесс считается активным, если одна или несколько команд выполнено, в то время как другие команды подлежат выполнению; в противном случае процесс является пассивным, или завершенным. Активный процесс называют выполняемым, если его команды последовательно реализуются с помощью процессора; противоположное состояние носит название ожидающего. Пассивный процесс можно рассматривать как всегда ожидающий.
Таблица 11.2. Возможные состояния процесса Р
АктивностьСостояниеОписание
Активный
Активный
ВыполняемыйЦП выполняет команды процесса Р
ОжидающийЦП выполняет команды некоторых
других процессов, но процесс Р может быть реализован, если использовать какой-либо другой способ
Пассивный
ОжидающийЦП выполняет команды других про
цессов, и прерывания, инициирующего выполнение процесса Р, произойти не может. Процесс Р может начать выполняться, только если другой процесс явным образом активирует его,
В табл. 11.2 представлены возможные состояния процессов.
Рассмотрим, например, программу вывода на дисплей, которая приведена на рис. 11.2. Она содержит два процесса: главную программу (МРР) и процесс вывода на дисплей строки (SPP). Периодически программа МРР активирует процесс SPP путем вызова подпрограммы STPOUT. После вывода одного символа на дисплей процедура STPOUT временно прекращает (или переводит в ожидание) процесс SPP и производит возврат управления в главную программу. После этого выпол-
некие процесса SPP периодически возобновляется прерываниями дисплея, которые обслуживаются подпрограммой STRINT. Процесс SPP переводится в ожидание, когда подпрограмма STRINT осуществляет возврат управления прерванной программе, и завершается, когда выводится последний символ строки.
На рис. 11.3 показано состояние переменной SBUSY, соответствующей активному (1) или пассивному (0) состоянию процесса SPP; при этом программа МРР активна. Состояние выполнения или ожидания процессов можно получить, если проследить ход реализации программы. Процесс SPP находится в состоянии выполнения, когда процессор осуществляет выполнение подпрограмм STROUT или STRINT, а все остальное время он пребывает в состоянии ожидания. Главная программа МРР находится в состоянии выполнения тогда, когда процессу SPP присуще состояние ожидания, и наоборот.
Так как прерывания могут появляться в произвольные моменты времени, то выполняемый процесс в любой момент может перейти в состояние ожидания, а ожидающий процесс — в состояние выполнения. В пределах данного процесса команды выполняются в том порядке, в котором они были записаны, но между ними могут включаться в действие команды других процессов. На самом низшем уровне такое поведение обусловливает требование, чтобы содержимое всех регистров, отображающих состояние процессора, было сохранено и восстановлено с помощью программы обработки прерывания. Выполнению программ на более высоком уровне присущи некоторые особенности, например когда два или более процесса используют одни и те же команды или данные.
Введение ограничений
Параллельные процессы обычно выполняются под управлением операционной системы или исполнительной программы. В некоторых операционных системах допускается динамическое создание и разрушение процессов. В других требуется создание всех процессов во время инициализации системы. В последнем случае процессы всегда активны: они могут быть только выполняемыми или переведены в состояние ожидания и не могут быть активированы другим процессом или завершены. Рассматривая с этой точки зрения пример SPP/MPP, можно утверждать, что процесс SPP в действительности никогда не завершится, даже после вывода последнего символа в строке SPP, а просто перейдет в состояние ожидания. Но существуют два различных способа, с помощью которых процесс SPP можно вновь вернуть в состояние выполнения после того, как он находился в состоянии ожидания: либо путем прерывания, либо с помощью команды из другого процесса.
Этих положений мы будем придерживаться для упрощения анализа материала оставшейся части глав. Будем считать, что процессы всегда активны, но происходит смена состояний выполнения и ожидания. Процесс может быть переведен в состояние ожидания или выполнения в трех основных случаях:
Процесс переходит в состояние ожидания, если появляется прерывание от какого-либо устройства, и переходит в состояние выполнения, когда программа обработки прерывания передает ему управление.
Процесс переводится в состояние выполнения сигналом прерывания своего устройства и выполняет соответствующую программу обработки прерывания. Процесс переводится в состояние ожидания, когда он возвращает управление прерванной программе. Прежде чем перейти в состояние ожидания, процесс может принять меры для того, чтобы прерывание своего устройства впоследствии могло вновь перевести его в состояние выполнения.
Процесс может быть переведен в состояние выполнения командой другого процесса (например, командой начала печати строки текста). Процесс переходит в состояние ожидания при передаче управления вызывающему процессу, но прежде чем сделать это, он обычно принимает меры для того, чтобы прерывание своего устройства впоследствии могло вновь перевести его в состояние выполнения.
Совмещение процессов и процессы со многими прерываниями
Для более эффективного использования ресурсов ЭВМ везде, где это возможно, вычислительные операции должны быть совмещены во времени с операциями ввода-вывода. В программе вывода данных на дисплей (рис. 11.2) удачно применяется совмещение операций благодаря использованию процесса прерывания при завершении операций вывода, инициированных главной программой.
Обычно в программе имеется как процесс ввода, так и процесс вывода. Такая программа состоит из следующих шагов:
Чтение набора входных данных.
Обработка входных данных для получения набора выходных данных.
Запись набора выходных данных.
Если еще имеются входные данные, то возврат к шагу 1, если нет — завершение программы.
При отсутствии прерываний программа будет вынуждена выполнять каждый шаг отдельно, и у нее не будет периодов совмещения процессов, как показано на рис. 11.9, а. Цифры в кружках определяют каждый набор данных в порядке их чтения
устройством ввода, обработки центральным процессором и вывода.
Другая ситуация возникает при использовании прерываний (рис. 11.9,6). Главная программа должна ожидать появления первого набора входных данных. Однако процесс ввода может начать чтение второго набора входных данных, как только первый набор данных передан процессу обработки. Когда процесс обработки готов для обработки второго набора входных дан-
ВВод 0@@®
Обработка ®®©®
ВыбоЭ®@®®
а
Вво8 ® ®©® ©© ®
Обработка - _®_ _®__®_ J®.®®_® _
ВыбоЭ®®®__® ®®_
б
Рис. 11.9. Выполнение операций обработки и ввода-вывода:
•а — в отсутствие прерываний; б — при наличии прерываний.
ных, последние уже находятся в состоянии ожидания. Таким образом, значительная часть вычислительных операций совмещена во времени с операциями ввода-вывода. Иногда процесс обработки вынужден ожидать выполнения более медленных операций ввода-вывода (третий набор). После того как обнаруживается, что последний (седьмой) набор входных данных пустой, процесс обработки, прежде чем передать управление операционной системе, должен ожидать завершения текущей операции вывода.
Программа, в которой реализуется режим работы, указанный на рис. 11.9,6, приведена на рис. 11.10 на языке псевдоПаскаль. В программе выполняются три процесса: чтение строк входного текста, изменение порядка следования символов в каждой строке на обратный и печать полученных строк. Новое зарезервированное слово PROCESS вызывает процесс, а слова SLEEP и WAKEUP явным образом временно прекращают и возобновляют процесс. Процесс может находиться в состоянии SLEEP до тех пор, пока он не будет явно возбужден другим процессом посредством слова WAKEUP, или он может пребывать в состоянии SLEEP UNTIL до появления определенного условия (например, запроса на прерывание).
PROGRAM ReverseLines (input, output);
VAR inputBuffer, outputBuffer : ARRAY [1..100] OF char; inputAvailable, outputPrinted, working : boolean;
PROCESS Linein;
VAR kbdData : char; kbdIEN, kbdReady, kbdGo : boolean;
BEGIN
WHILE true DO {forever}
BEGIN
inputAvailable := false; ClearlnputBuffer;
REPEAT {Enable interrupt, request keystroke, clear ready.} kbdIEN := true; kbdGo := true; kbdReady := false;
SLEEP UNTIL kbdReady = true; {Wait for keyboard interrupt.} PutlnlnputBuffer(kbdData);
UNTIL kbdData=terminator;
kbdIEN := false; inputAvailable := true; ^LEEP;
END; {WHILE}
END; {Linein}
PROCESS LineOut;
VAR dspData : char; dspIEN, dspReady, dspGo : boolean;
BEGIN
WHILE true DO {forever}
BEGIN
outputPrihted := false;
WHILE CharsLeftToBePrinted DO {if there’s an output char...} BEGIN {...send char to display data register,...}
dspData := GetNextOutputChar;
{...enable interrupt, start display, and clear ready.} dspIEN := true; dspGo := true; dspReady := false;
SLEEP UNTIL dspReady = true; {Wait for display interrupt.} END;
dspIEN := false; outputPrinted := true; SLEEP;
END; {WHILE}
END; {LineOilt}
PROCESS MainLoop;
LABEL 10; VAR workBuffer : ARRAY [1..100] OF char;
BEGIN
WAKEUP Linein; {Request Linein to read the first line...}
WHILE true DO{...and put it in inputBuffer.}
BEGIN
SLEEP UNTIL inputAvailable; {Wait for an input line.} IF InputBufferEmpty THEN GOTO 10; {Exit on blank line.} workBuffer := inputBuffer; {Copy input line...} WAKEUP Linein; {...and start reading next line.} ReverseWorkBuffer; {Reverse characters in work buffer.} SLEEP UNTIL outputPrinted; {Wait for a chance to print.}; outputBuffer := workBuffer; {Copy line...} WAKEUP LineOut; {...and start printing it.}
END; .
. 10: SLEEP UNTIL outputPrinted; {Wait for last line to be printed.} working := false; SLEEP {forever};
END;
BEGIN {Main program.}
working : = true; outputPrinted := true; {initialization.}
WAKEUP MainLoop; WHILE working DO ; {Wait for MainLoop to-finish.} END.
Рис. 11.10. Программа изменения порядка следования символов в строке- входного текста на обратный.
В программе значение регистров управления и состоянияг клавиатуры и дисплея представляются в виде булевых переменных, а регистры данных — как символьные переменные. Интерфейсы устройств могут изменять значения этих переменных (например, интерфейс клавиатуры при получении символа устанавливает переменную kbdReady в 1). Определения некоторых процедур и функций предоставляем читателю дать самостоятельно.
Главная программа возбуждает процесс MainLoop и переходит в режим «занято-ожидание», пока MainLoop не закончит все операции. Процесс MainLoop выполняет цикл из трех шагов (ввод, обработка, вывод) и переходит в режим ожидания, если процессы ввода или вывода не завершили свою работу к моменту, когда процесс MainLoop готов инициировать следующую операцию ввода или вывода.
Особенности реализации
Процессы, представленные на рис. 11.10, имеют непосредственное отношение к сопрограммам. При возобновлении процесса после ожидания вычислительные операции должны продолжаться с того места, где он был остановлен, так же как и в случае использования сопрограммы, работа которой возобновляется после вызова и окончания другой сопрограммы. В вер-
PROCEDURE StartLineOut; {Display interrupt initialization routine.1 BEGIN
outputPrinted := false; dspData := GetNextOutputChar;
{Enable interrupt, start display, and clear ready.} dspIEN := true; dspGo : = true; dspReady := false;
END;
PROCEDURE Dsplntr; {Called by interrupt system when (dspIEN=true) AND (dspReady=true) }
BEGIN {Display interrupt service routine.}
, IF CharsLeftToBePrinted THEN
BEGIN {Send character to display data register,...}
dspData := GetNextOutputChar;
{...enable interrupt, start display, and clear ready.} dspIEN := true; dspGo := true; dspReady := false;
END;
ELSE BEGIN dspIEN := false; outputPrinted := true END;
END;
Рис. 11.11. Процедурная версия процесса LineOut.
сии программы на языке ассемблера можно предусмотреть переменные, которые запоминают адреса возврата для каждого лроцесса.
Возможно моделировать более общую структуру сопрограмм с помощью подпрограмм. На рис. 11.11 показано, как можно закодировать процесс LineOut, прибегнув к двум процедурам. Вместо использования оператора WAKEUP LineOut для вывода строки главная программа вызывает процедуру StartLineOut. Потом каждый раз, когда имеет место прерывание дисплея, система прерываний вызывает процедуру Dsplntr. Эти две процедуры псевдоПаскаля имеют точно такую же структуру, как и подпрограммы STROUT и STRINT микропроцессора 6809, показанные на рис. 11.2.
Выполнение и ожидание
Независимо от того, что используется для кодирования процессов— сопрограммы или подпрограммы, необходимо знать способы перевода процесса в состояния выполнения и ожидания. Предположим, что вся программа работает под управлением операционной системы или исполнительной программы таким образом, что главная программа переводится в состояние выполнения в первый раз, когда исполнительная программа запускает ее, и переходит в конечное состояние покоя при окончательной передаче управления исполнительной программе.
Процесс MainLoop (рис. 11.10) может активировать процесс прерывания Linein или LineOut, вызывая подпрограмму, которая разрешает прерывание устройства, запрашивает первую операцию ввода-вывода и производит возврат управления процессу MainLoop. Впоследствии процессы Linein и LineOut периодически переходят в состояние выполнения с помощью прерываний; каждый из них переводит себя в состояние ожидания, запрашивая очередную операцию ввода-вывода и возвращая управление прерванной программе. В том случае, когда процессы Linein и LineOut в конце концов возвращают управление прерванной программе без запрашивания очередной операции ввода-вывода, они могут быть вновь активированы явным образом только с помощью другого процесса.
Возникает вопрос: как процесс MainLoop может перевести себя в состояние ожидания, тогда как он не может осуществить прерывания самого себя? Если есть операционная система, MainLoop может временно вернуть ей управление, разрешив операционной системе планировать другую полезную работу, в то время как процесс MainLoop находится в состоянии ожидания. При отсутствии такой возможности процесс MainLoop может просто войти в цикл «занято-ожидание». В любом случае процесс MainLoop должен полагаться на другие процессы, ко-
торые активируют его. Пусть, например, в процессе MainLoop используется глобальная булева переменная mainWaitingForlnput для указания, что этот процесс уже перешел в состояние ожидания операции ввода. Тогда каждый раз, когда процесс Linein оканчивает чтение строки, он должен проверять значение переменной mainWaitingForlnput для того, чтобы определить, должен ли быть возбужден процесс MainLoop. Типичная последовательность событий при допущении, что обработка одной строки осуществляется гораздо быстрее, чем операция ввода, следующая:
В процессе MainLoop определяется, что значение переменной inputAvailable ложное (состояние 0). Поэтому переменная mainWaitingForlnput устанавливается в состояние 1, а MainLoop переходит в состояние ожидания.
Процесс Linein переходит в состояние выполнения, осуществляет чтение символа и переводится обратно в состояние ожидания. Этот шаг может отсутствовать или повторяться несколько раз.
Процесс Linein переходит в состояние выполнения, осуществляет чтение последнего символа строки и устанавливает переменную inputAvailable в 1. Далее проверяется значение переменной mainWaitingForlnpute, и если она находится в состоянии 1, процесс MainLoop переводится в состояние выполнения, a Linein— в состояние ожидания.
Процесс MainLoop переводится в состояние выполнения, осуществляется копирование новой входной строки и процесс Linein также устанавливается в состояние выполнения.
Выполнение процесса MainLoop продолжается, затем происходит возврат к шагу 1.
Ошибки синхронизации и критические секции
Применение предложенного метода приостанавливания и возобновления процесса MainLoop представляется целесообразным, однако возможно возникновение ошибок из-за состязаний. Рассмотрим последовательность операций, которая будет иметь место в том случае, если время обработки и время ввода строки одинаково. •
В процессе MainLoop определяется, что переменная input Available равна 0. Поэтому переменная mainWaitingForlnput устанавливается в 1, и процесс MainLoop готов перевести себя в состояние ожидания.
Процесс Linein переходит в состояние выполнения, осуществляет чтение последнего символа строки и далее устанавливает переменную InputAvailable в 1. Далее с помощью процесса Linein производится проверка переменной mainWaiting For Input и, если ее значение равно 1, процесс Linein осущест-
вляет вызов процесса MainLoop и переводит себя в состояние ожидания.
Активируется процесс MainLoop, который сразу же переводит себя в состояние ожидания, как и было запланировано.
Теперь как процесс MainLoop, так и процесс Linein ожидают, когда один из них переведет в состояние выполнения другой; в этом состоянии ожидания они находятся бесконечно.
Возникшая ситуация, когда происходит взаимная блокировка двух процессов, называется тупиковой. Два процесса взаимно заблокированы, если выполнение каждого из них не может быть продолжено до тех пор, пока не будет продолжено выполнение другого процесса. В данном случае такими процессами являются MainLoop и LineOut.
Команды процесса MainLoop, осуществляющие проверку значения переменной InputAvailable и корректировку переменной mainWaitingForlnput, называют критической секцией. Критическая секция процесса — это набор команд, где используются переменные, которыми оперируют еще один или несколько других процессов; при этом возможно изменение результата выполнения процессов.
Для предотвращения взаимной блокировки и других нарушений синхронизации критическая секция должна выполняться от начала до конца без какого-либо вмешательства команд дру- го процесса. Иногда это называют взаимным исключением процессов. '
Для обеспечения «взимного исключения» в программах на языке ассемблера в начале выполнения критической секции производится отключение системы прерываний процессора; включают эту систему в конце выполнения критической секции.
Обнаружение критических секций
Мы дали определение критических секций, но не предложили какого-либо правила их обнаружения. В общем случае для нахождения критических секций требуется провести тщательный анализ программы и ее внешних условий. Например, две последние команды процесса Linein могут оказаться критической секцией в следующих случаях:
Процесс Linein переходит в состояние выполнения, с его помощью осуществляется чтение последнего символа входной строки, значение переменной inputAvailable устанавливает в 1. Процесс Linein готов перевести себя в состояние ожидания при выполнении условия 2.
Процесс MainLoop переводится в состояние выполнения, определяется, что переменная inputAvailable имеет значение 1, при этом копируется входная строка и процесс Linein переводится в состояние выполнения.
Процесс Linein переходит в состояние выполнения и сразу же, как и планировалось, переводит себя в состояние ожидания без чтения следующей строки.
Однако в большинстве приложений этого не может случиться, так как процесс Linein возбуждается прерыванием, и процесс MainLoop может возобновиться только по завершении программы обработки прерывания в процессе Linein. В других приложениях возможно возникновение критической секции, если, например, процессы Linein и MainLoop реализуются одновременно на двух различных процессорах, в которых используются одни и те же переменные, хранимые в общей основной памяти.
При возможности существования критических секций наилучшим способом действия может оказаться блокировка прерываний. Однако к решению этого вопроса следует подходить осторожно по следующим двум причинам:
Блокировка прерываний на длительное время может иметь неблагоприятное влияние на производительность системы ввода-вывода, что чревато потерей информации при вводе и задержкой вывода.
Неправильно организованная блокировка прерываний мажет привести к взаимной блокировке.
Блокировка прерываний на короткое время обычно не приводит к потере входных данных, так как наибольшее число интерфейсов устройств поддерживают запросы на прерывание в состоянии ожидания до тех пор, пока эти запросы не будут обслужены. Однако если в то время, когда запрос на прерывание от первого входного воздействия находится в состоянии ожидания, придет запрос на прерывание от второго входного воздействия, то в обычных интерфейсах это приводит к потере второго запроса на прерывание.
Для иллюстрации тупиковой ситуации предположим, что блокировка системы прерываний в процессе MainLoop (рис. 11.10) вводится перед первым оператором SLEEP, а деблокировка — после проверки переменной InputBufferEmpty. Тогда если процесс MainLoop оказывается в состоянии ожидания входного воздействия, то он должен ожидать при блокированных прерываниях. При этом может оказаться, что процесс MainLoop никогда не будет вновь переведен в состояние выполнения, так как для этого требуются прерывания от входного воздействия. Таким образом, необходимо еще раз подчеркнуть, что написание и структурирование программ с параллельными процессами требуют тщательного анализа программ и условий их работы.
11.5.8. Блокировка и семафоры
Другим способом защиты критических секций программ является блокировка общих ресурсов таким образом, что в опре-
деленное время они могут использоваться только одним процессом. Это можно сделать, снабдив ресурсы флажками или семафорами, которые показывают, что те или иные ресурсы в данный момент подлежат использованию. Процесс, который имеет доступ к общим ресурсам, применяет семафоры следующим образом:
В процессе проверяется текущее значение семафора. Если оно равно 0, процесс может переходить к выполнению следующего шага. Ненулевое значение семафора указывает на то, что в настоящее время общие ресурсы используются другим процессом. В этом случае текущий процесс должен перейти в состояние ожидания до тех пор, пока другой процесс не установит этот семафор в 0. .
Процесс может теперь управлять ресурсами и исключить воздействие других процессов путем установления семафора в ненулевое значение.
Процесс использует общие ресурсы, а затем освобождает их для других процессов, устанавливая семафор в 0.
При этом должна быть уверенность, что процесс, переключенный в режим ожидания по семафору, будет в конечном итоге переведен в состояние выполнения с помощью либо прерываний, либо операционной системы, которая следит за работой семафоров и за процессами, находящимися в состоянии ожидания. В том случае, если процесс находится в режиме ожидания и если заблокирована система прерываний, происходит не только взаимное исключение процессов, но и их взаимная блокировка.
Эта проблема встречается при написании программ на языке ассемблера с ранее описанными операциями с использованием семафоров. Рассмотрим следующую программу для Z8000:
CHECK TEST
SEMAPH
Требуется MSB = 0==>- ресурс доступен
JR
MI,CHECK
Ожидание ресурса
DEC
SEMAPH
Установка семафора в 1
Использование ресурса
CLR
SEMAPH
Освобождение ресурса для других пронес-
сов
Предположим, что в двух различных процессах содержится фрагмент программы, реализующий доступ к общим ресурсам с использованием семафора SEMAPH. Ниже представлена схема последовательности событий, приводящих к взаимной блокировке.
СНЕСКА TEST JR DECCLRСНЕСКА TEST JR DECCLRSEMAPHMI, CHECKS SEMAPHSEMAPHSEMAPHMI, CHECKS SEMAPHSEMAPHSEMAPH \ ^iET^L--^^^ TEST mi, checka^^^L JR
СНЕСКА TEST JR DEC
CLR
СНЕСКА TEST JR DEC
CLR
SEMAPH
MI, CHECKS SEMAPH
SEMAPH
SEMAPH
MI, CHECKS SEMAPH
SEMAPH
SEMAPH |'—E C
SEMAPH" (CLR
В процессе А определяется, что SEMAPH = О, но перед установкой семафора в 1 происходит прерывание и процесс В получает доступ к общим ресурсам, так что значение переменной SEMAPH еще равно 0. Далее, если управление опять перейдет к процессу А до того, как процесс В освободит общие ресурсы, то возникнет ситуация, когда оба процесса одновременно имеют доступ к общим ресурсам. Это может произойти, если процесс В отказывается от управления, ожидая ввода-вывода. Аналогичная ситуация имеет место, когда два процесса выполняются одновременно на двух процессорах и выбирают SEMAPH из общей памяти.
Проблема может быть решена, если проверка и установка семафора осуществляются с помощью одной неделимой операции. Для того чтобы сделать это, во многих процессорах предусматривается команда TSET (TEST and SET), которая осуществляет чтение операнда из памяти, производит соответствующую установку разрядов состояния и установку всех ячеек памяти в 1. Для микропроцессора Z8000 можно записать процедуру проверки общих ресурсов следующим образом:
CHECK TSET JRCLRCHECK TSET JRCLRSEMAPHТребуется М5В = 0==ф- ресурс доступен
CHECK TSET JR
CLR
CHECK TSET JR
CLR
NE,CHECK Ожидание ресурса
Ресурс доступен, получение и использование его
SEMAPH Освобождение ресурса для других процессов
В этой программе команда TSET всегда производит установку SEMAPH в 1 даже в том случае, если SEMAPH уже содержит 1. Поэтому на протяжении выполнения команды TSET общие ресурсы либо становятся заблокированными, либо продолжают такими оставаться. Управление передается по команде условного перехода, и новый процесс получает управление ресурсами только в том случае, если старший значащий бит (MSB) переменной SEMAPH перед его установкой находился в состоянии 0.
Вероятно, у вас уже сложилось мнение, что существует гораздо больше способов управления процессами, чем кажется вначале. Полное обсуждение связи процессов и их синхронизации выходит за рамки этой книги. Этим вопросам посвящена обширная литература (см. раздел в конце главы). Пример программы с тремя процессами, написанной на языке ассемблера, будет представлен в разд. 11.6.3.
11.6. ИСПОЛЬЗОВАНИЕ ДАННЫХ
И КОМАНД НЕСКОЛЬКИМИ ПРОЦЕССАМИ
При наличии большого числа процессов (прерываний) два или большее число процессов могут совместно использовать одни и те же данные или переменные. В программах с таким режимом работы должны быть приняты специальные меры.
Структура данных, используемых несколькими процессами •
Выше рассматривалась программа, содержащая три процесса, для синхронизации работы которых использовались переменные, хранимые в памяти. Критические секции появлялись в том случае, когда оба процесса одновременно осуществляли обра-
iВеличина времени формируется из сигналов переменного напряжения сети питания частотой 60 Гц, производя прерывания каждые 1/60 с, если разряд IEN=1. Программа обработки прерывания использует эти TICKS для сохранения текущего времени около 1/60 с, используя глобальные переменные TICK, second, minute и hour|
PROCEDURE Timerlntr; {Called by interrupt system if (timerIEN=true) AND (timerReady~true)} BEGIN {Timer interrupt service routine.} v tick :- tick + 1;
' IF tick=60 THEN
BEGIN tick := 0; second := second + 1;
IF second = 60 THEN
BEGIN second := 0; minute := minute + 1;
IF minute = 60 THEN.
BEGIN minute := 0; hour := hour + 1;
IF hour = 24 THEN.
BEGIN hour := 0; IncrementDayAndDate END;
END;
END;
END; {Now request next timer tick.} "
t liter TEN := true; timerGo := true; timerReady := false; END;
Рис. 11.12. Программа обработки прерывания для программной реализации функции часов. *
щение к одним и тем же переменным, что приводило к возникновению тупиковой ситуации.
Критические секции могут появляться и в других случаях использования одних и тех же данных, хотя последствия могут оказаться не столь серьезными, как возникновение взаимной блокировки. Рассмотрим, например, фрагмент программы обработки прерывания на языке Паскаль, реализующей функцию часов (рис. 11.12). Пусть главная программа осуществляет чтение текущего времени дня с помощью следующей последовательности операторов:
seel := second; mini := minute; hourl := hour;
Ошибка может возникнуть, если прерывание таймера произойдет в середине этой последовательности. Например, пусть текущее время (часы, минуты, секунды) составляет 7:05:59 и
после выполнения первого оператора появилось прерывание таймера. Тогда, когда процессор завершит реализацию вышеуказанной последовательности операторов, окажется записанным время 7:06: 59. Возможно появление даже более значительных ошибок во времени, например 7:59: 59. Чтобы исключить возникновение таких ошибок, прерывания таймера должны быть заблокированы в программе на время чтения времени.
Рассмотренные переменные времени представляют собой простую структуру данных. Выключение прерываний или иной способ, предотвращающий одновременную выборку данных параллельными процессами, называется блокировкой закрытием структуры данных. В том случае, когда два или большее число процессов одновременно имеют доступ к незакрытой структуре данных, возможно возникновение трех ситуаций, которые могут привести к появлению ошибок:
Если структура данных не закрыта для чтения, то процесс может прервать и модифицировать структуру данных во время чтения ее другим процессом. В этом случае процесс, осуществляющий чтение данных, может получить противоречивую информацию.
Если структура данных не закрыта для модификации, то какой-либо процесс может прервать процесс, осуществляющий модификацию, и выполнить чтение структуры данных во время модификации. В этом случае процесс, осуществляющий чтение, может также получить притиворечивую информацию.
Если структура данных не закрыта для модификации, то два процесса могут одновременно пытаться корректировать структуру данных, что может привести к нарушению целостности структуры данных.
Заметим, что если два процесса одновременно осуществляют чтение структуры данных, то ошибки не появляются. Мы уже рассмотрели пример, относящийся к первому случаю. Иллюстрацией второго случая является пример, когда программа обработки прерывания таймера сама должна прерываться другим процессом, осуществляющим чтение времени. При переходе времени из 7:05:59 в 7 : 06 : 00 возможно считывание времени 7:05: 00. Хотя процессы как в первом, так и во втором случае осуществляют чтение неправильного значения времени, целостность структуры данных не нарушается.
Чтобы пояснить третий случай, предположим, .что главная программа осуществляет формирование значения текущего времени дня с помощью следующей последовательности команд: read (hour 1,mini,seel); hour := hourl minute := mini second := seel
Пусть время, показываемое часами, 1 : 59 : 59, и пользователь пытается переставить часы на время 3:00:00 (например, перейти на летнее время). В том случае, если между второй и третьей строками появится прерывание таймера, будет установлено неверное значение времени 4:00: 00.
При использовании сложных структур данных могут возникать более серьезные ошибки. Например, обычно прерывание процесса, корректирующего связанный список для выполнения другой операции корректирования этого же списка, приводит к катастрофическим последствиям. Это может вызвать формирование нежелательных кольцевых списков, расчлененных подсписков без указателей и другие неприятные осложнения.
В редких случаях может оказаться возможным, что два процесса одновременно модифицируют одну структуру данных, не приводя к появлению ошибок. Предположим, имеются два процесса, осуществляющих доступ к очереди, причем один процесс помещает данные в очередь, а другой исключает их. Если очередь снабжена указателями начала и конца очереди (разд. 3.4), то се целостность сохраняется, так как один процесс модифицирует начало, а другой — конец очереди1). Однако в целях обеспечения сохранности структуры данных, к которым может быть осуществлен доступ из параллельных процессов, они должны всегда блокироваться.
11.6.2. Реентерабельные программы
Рассмотрим подпрограмму, которая может быть вызвана двумя или большим числом процессов. После того как подпрограмма вызвана одним процессом, она может быть «повторно вызвана» другим процессом, прежде чем первый закончит ее выполнение. Подпрограммы, которые дают правильные результаты при таких повторных вызовах, называются реентерабельными.
Пусть в микропроцессоре 6809 в главной программе и одной или большем числе программ обработки прерывания используется подпрограмма DIVIDE. Предположим, что главная программа осуществила вызов подпрограммы DIVIDE, в середине выполнения которой появилось прерывание от устройства вывода. Пусть также программа обработки прерывания реализует алгоритм преобразования двоичных чисел в код ASCII, в кото
]) Худшее, что может случиться, — это когда процесс исключения элемента из очереди «считает», что очередь пуста, в то время как процесс включения элемента в очередь реализует свою функцию, или наоборот. Однако целостность очереди сохраняется, так что процесс исключения из очереди обнаружит вновь установленные в очередь данные только после того, как еще раз проверит очередь, как будто процесс включения элемента в очередь произошел по времени чуть позже, чем на самом деле.
ром вычисление цифр осуществляется посредством последовательного деления на 10. В этом случае программа обработки прерывания осуществит повторный вызов подпрограммы DIVIDE и будет выполнять ее до конца, прежде чем возобновится работа подпрограммы DIVIDE в главной программе.
Не возникает никаких неприятных последствий из-за того, что несколько команд выполняются «одновременно» двумя процессами. Так как команды в несамомодифицирующихся программах «только считываются» и изменению не подлежат, по-
-37-
DVND
-96-
DVND
-96-
DVND
8
DVSR
10
DVSR
10
DVSR
—
QUOT
—
QUOT
9
QUOT
—
REM
—
REM
6
REM
5
CNT
5
CNT
0
CNT
а
б
6
Рис. 11.13. Параметры при повторном вызове нереентерабельной подпрограммы DIVIDE:-
а — деление 37 на 8 до прерывания; б — деление 96 на 10 при повторном входе в подпрограмму DIVIDE; в —при возврате из прерывания.
следовательность команд одинакова для обоих процессов. Каждый процесс по мере выполнения может формировать различные наборы значений, хранимых в регистрах процессора, но это не вызывает никаких осложнений в том случае, когда процесс прерывания осуществляется обычным образом путем записи состояния регистров в стек и извлечения их из стека при выходе из прерывания. Проблемы возникают, когда команды обращаются к переменным, помещенным в фиксированные ячейки памяти. '
Рассмотрим, например, подпрограмму DIVIDE для микропроцессора 6809 (рис. 6.7). Четыре параметра, находящиеся в фиксированных ячейках памяти, передаются в подпрограмму (DVND, DVSR, REM, QUOT) и еще одна ячейка используется для запоминания локальной переменной (CNT). Если выполнение подпрограммы приостанавливается прерыванием, значения регистров и адресов возврата будут сохранены в стеке и полностью восстановлены при возврате. Однако если подпрограмма DIVIDE вызывается опять программой обработки прерывания, то старые значения параметров и локальной переменной будут утеряны навсегда. На рис. 11.13 показано, как это происходит. Оставшиеся вычисления, которые должны быть сделаны при первом вызове подпрограммы DIVIDE, будут возобновлены с неправильными данными, и результат будет неверным. Вообще,
всегда, когда в подпрограммах для размещения параметров или локальных переменных используются фиксированные ячейки памяти, при повторном использовании этих подпрограмм неизбежно появление ошибок.
На рис. 9.18 показана подпрограмма DIVIDE, которая работает правильно при повторном использовании, — это реенте-
SPSPаа^рFP^рFPSPSPCRT _(M _ FP Return_ Addr. DVSR DVNDCRT _(M _ FP Return_ Addr. DVSR DVNDFP-FP-55REM аиотREM аиот. Saved."^Regs.-. Saved."^Regs.-ввREMQUOTREMQUOTCNT- _OLd _ FPReturn~Addr~DVSRDVNDCNT- _OLd _ FPReturn~Addr~DVSRDVNDРис. 11.14. Состояние стека при использовании реентеоабельной подпрограммы DIVIDE: *Рис. 11.14. Состояние стека при использовании реентеоабельной подпрограммы DIVIDE: *
SP
SP
а
а
^р
FP
^р
FP
SP
SP
CRT _(M _ FP Return_ Addr. DVSR DVND
CRT _(M _ FP Return_ Addr. DVSR DVND
FP-
FP-
5
5
REM аиот
REM аиот
. Saved.
"^Regs.-
. Saved.
"^Regs.-
в
в
REM
QUOT
REM
QUOT
CNT
- _OLd _ FP
Return
~Addr~
DVSR
DVND
CNT
- _OLd _ FP
Return
~Addr~
DVSR
DVND
Рис. 11.14. Состояние стека при использовании реентеоабельной подпрограммы DIVIDE: *
Рис. 11.14. Состояние стека при использовании реентеоабельной подпрограммы DIVIDE: *
а — выполнение программы MAIN; б — выполнение подпрограммы DIVIDE, вызванной из программы MAIN; в — выполнение подпрограммы INTSVC; г — выполнение подпрограммы DIVIDE, вызванной из подпрограммы INTSVC.
рабельная программа. Так как все параметры и локальные переменные помещены в стек, для каждого вызова подпрограммы там же в стеке размещается их новый набор. На рис. 11.14 изображено содержимое стека во время выполнения программы с использованием этой версии подпрограммы DIVIDE. Значения параметров и локальных переменных во время первого вызова подпрограммы DIVIDE запоминаются в стеке во время выполнения программы обработки прерывания и сохраняются там во время второго (повторного) вызова подпрограммы DIVIDE.
В программе DIVIDE, показанной на рис. 9.15, осуществляется передача параметров в область параметров. Эта програм-
ма «почти реентерабельная», если в каждом из вызывающих-ее процессов используются различные области параметров. Возникает затруднение с локальной переменной CNT, так как она используется всеми вызывающими программами. Это затруднение можно разрешить путем сохранения значения в дополнительном регистре процессора (Y) или в стеке либо введением требования, что вызывающая программа должна зарезервировать для CNT 1 байт в области параметров.
Подпрограммы обработки очередей, представленные на рис. 9.21, являются реентерабельными, так как в них нет локальных переменных и параметров, полностью передаваемых в регистры.
Наилучшим способом создания реентерабельных подпрограмм является использование только регистров и стековой памяти для сохранения всех параметров и локальных переменных. При этом осуществляется динамическое перераспределение, насколько это необходимо, пространства памяти, отведенного для параметров и локальных переменных. Подпрограммы, для реализации которых используется стековая структура (разд. 9.3.6), являются реентерабельными в том случае, если работа каждой вызванной подпрограммы уже завершается перед повторным вызовом более высокого уровня. Это обычно происходит в системах с одним источником прерываний (монопольным пользователем). В других случаях, например в системах коллективного пользования, выполнение программ может приостанавливаться и возобновляться в любой точке. Тогда для каждого процесса должны быть обеспечены раздельные области стековой памяти.
Реентерабельные программы важны при создании программного обеспечения систем, особенно систем коллективного пользования, где один процесс связан с каждым пользователем. Укажем типичные применения реентерабельных программ.
Программы-утилиты, такие, как программы преобразования двоичных чисел в код ASCII, должны быть реентерабельными, потому что они могут быть одновременно вызваны различными пользователями (процессами).
Если большие программы коллективного пользования — компиляторы или редакторы текста — являются реентерабельными, их можно запомнить в памяти только один раз; при этом ими одновременно могут оперировать разные пользователи с различными областями данных.
В группе идентичных устройств ввода-вывода, например дисплеев на электронно-лучевых трубках, может применяться одна реентерабельная программа обработки прерывания, использующая для каждого устройства отдельную область данных в буфере и таблицу конфигурации (спецификации вычислительных средств).
11.6.3. Пример программы на языке ассемблера
На рис. 11.15 представлена общая структура программы, которой свойственны параллельные процессы, критические секции и общие для различных процессов команды и данные. Программа содержит три процесса, реализующие ввод, обработку и вывод. Операции ввода-вывода осуществляются посимвольно, и в главной программе происходят посимвольное чтение и запись данных. Для того чтобы выровнять потоки в процессах
Рис. 11.15. Структура программы, использующей очереди для буферирования ввода-вывода.
ввода, вывода и обработки данных, для буферирования входных и выходных символов используются очереди. Тогда, если главная программа работает медленнее процесса ввода, устройство ввода может быть постоянно занято и осуществлять «просмотр вперед» и заполнение входной очереди. Аналогично во время резкого увеличения скорости вычислений главная программа может выдавать значительное количество символов в выходную очередь, не дожидаясь, пока они будут выведены. Таким образом, эти три процесса можно представить следующим образом:
Процесс ввода получает символы из устройства ввода и помещает их во входную очередь.
Главный процесс получает символы из входной очереди, обрабатывает их и помещает результат в выходную очередь.
Процесс вывода получает символы из выходной очереди и посылает их в устройство вывода.
Программа для микропроцессора 6809, написанная на языке ассемблера, представлена на рис. 11.16. Две очереди (IQ и OQ) описаны в соответствии со спецификациями на модули очередей (рис. 9.21). Подпрограммы, реализующие модули очереди, соответственно QINT, QENQ и QDEQ. Главная программа инициализирует две очереди, переводит процесс вывода в состояние ожидания до появления данных в очереди OQ и активирует процесс ввода для вызова первого символа. Далее она входит в цикл обработки, который осуществляет чтение и запись символов в необходимом порядке.
Первоначально процесс ввода активируется с помощью подпрограммы IWAKE, которая осуществляет запрос одного символа из устройства ввода. В дальнейшем процесс ввода активируется с помощью сигналов запроса на прерывание устройства ввода. Программа обработки прерывания ISVC помещает каждый символ в очередь IQ. Если устройство ввода очень быстрое
или процесс MAIN работает медленно, то при выполнении программы ISVC может оказаться, что очередь IQ заполнена. В этом случае процесс ввода сохраняет текущее значение входного символа в переменной SAVEIN, устанавливает переменную WAITIQ в 1, чтобы показать, что он ожидает свободного места в очереди IQ, и переводит процесс вывода в режим «ожидания без запрашивания ввода. Затем, когда процесс MAIN осуществит перемещение символа из очереди IQ, он включает сохраненное значение входного символа SAVEIN в очередь IQ и вновь возбуждает процесс ввода, вызывая подпрограмму IWAKE.
с
0Q
RMB
10
Output queue.
OQDT
RMW
2
Output queue descriptor table.
FCW
0Q,0Q+9
IQ
RMB
10
Input queue.
IQDT
RMW
2
Input queue descriptor table.
FCW
IQ,IQ+9
WAITOQ
RMB
1
Nonzero when output process is waiting
*
for a character to appear in OQ.
WAITIQ
RMB
1
Nonzero when input process is waiting
*
for space in IQ.
SAVEIN
RMB
1
Save input character when waiting
STACK
RMB
20 -
for room in IQ.-
STACKE-
EQU
*
Return address Stack.
*
MAIN
LDS
#STACKE
Initialize return-address stack.
LDX
#0QDT
Initialize output queue...
JSR
QINIT
LDX
#IQDT
...and input queue.
JSR
QINIT
LDA
#1
Mark output processing waiting for a
STA
WAITOQ
character to print.
JSR
IWAKE
Wake up input and request first char.
LOOP
JSR
INCHR
■ Main processing loop.
JSR
INCHR
Read and write as many characters
. . .
as desired.
JSR
OUTCHR
'JSR
INCHR
JSR
OUTCHR
JMP
LOOP‘
Repeat the processing loop.
Come here when all done processing.
EXIT
JSR
INOFF
Turn off input interrupts.
WTOUT
TST
WAITOQ
Busy-wait until last char printed.
•BEQ
WTOUT
JMP
1000H
Return to operating system.
Рис. 11.16. Программа на языке ассемблера с использованием очередей для буферирования ввода-вывода.
Аналогичная ситуация имеет место и при реализации процесса вывода. Процесс MAIN вызывает подпрограмму OWAKE для печати первого символа из очереди OQ. Последующие символы перемещаются из очереди OQ и выводятся на печать с помощью программы обработки прерывания устройства вывода OSVC. В этом случае, если очередь OQ пуста, переменная WAITOQ устанавливается программой OSVC в 1, а сама программа OSVC переводится в состояние ожидания без печати следующего символа. Далее, когда процесс MAIN помещает следующий символ в очередь OQ, он опять активирует процесс вывода, вызывая подпрограмму OWAKE.
Для детального управления чтением и записью каждого символа путем манипулирования очередями и перевода процессов в состояния выполнения и ожидания процесс MAIN вызывает две подпрограммы: INCHR и OUTCHR. Подпрограмма INCHR, например, пытается извлечь символ из очереди IQ. Если это не удается, она просто переходит в режим ожидания до появления -символа в очереди IQ. В любом случае, после того как подпрограмма INCHR осуществит вызов QDEQ, в очереди IQ дол-
-*
Subroutine to get one input character from input queue.
INCHR
LDX . #IQDTGet a character from the input queue.
SETI*** Critical section — interrupts off.
JSRQDEQ
PSHS A,CCSave char and queue status (Z-bit).
TSTWAITIQInput queue must have room for
BEQINCH4at least one more character how,
LDASAVEINso if input process was waiting on
LDX#IQDTqueue full,then
JSRQENQenqueue thesavedcharacter...
BEQWHAAAT(impossibleerrorif queue full)
JSRIWAKE...and wakeup the input process.
INCH 4
PULSCC,ARestoreinput char and old queue status.
CLRI*** Endcritical sec. — interrupts on.
BEQINCHRCheck Z— did we really get character?
RTSReturn if we did, else try again.
WHAAAT *
JMP1800HImpossible error, return to oper. sys.
*
Subroutine to put one output character into output queue.
CUTCHR
PSHSASave output character.
LDX#OQD1Try to enqueue it.
SETI*** Critical section — interrupts off.
JSRQENQ v
PSHSCCSave queue status.
TSTWAITOQOutput queue must have...
BEQOUTCH4...at least one characterin it now...
JSROWAKE...so wake up output if it was waiting.
OUTCH4
PULSCC,ARestore character and queue status.
CLRI*** End critical sec. — interrupts on.
BEQOUTCHRCheck Z — did we really enqueue char?
RTSReturn if we did, else try again.
*
Device-dependent declarations and subroutines.
ICS
EQU
0F000H
Input device control and status reg.
IDATA
EQU
0F001H
Input device data register.
OCS
EQU
0F010H
Output device control and status reg.
ODATA
EQU
0F011H
Output device data register.
* Wake
up input process and
request one character.
IWAKE
CLR
WAITIQ
Mark the input process not waiting.
LDA
#41H
Set IEN and GO...
STA
ICS
...thereby requesting a character.
RTS
* Interrupt
service routine
for input device.
I SVC
LDA
IDATA
Get character.
STA
SAVEIN
Save it for possible later use.
LDX
#IQDT
Put into input queue.
JSR
QENQ
BEQ
IQFUL
Queue full?
LDA
#41H
No, set IEN and GO...
ISVC8
STA
ICS
...thereby requesting next character.
RTI
Return to interrupted program.
IQFUL
JSR
INOFF
Disable interrupts from input device.
LDA
#1
Put the input process to sleep...
STA
WAITIQ
...until there's room in IQ.
RTI
Return to interrupted program.
INOFF
CLRASubroutine to turn off input device.
STA
ICS
RTS
* Wake
up output process and print one character.
OWAKE
CLR
WAITOQ
Mark the output process not waiting.
LDX
#OQDT
Get a character from the output queue
JSR
QDEQ
BEQ
WHAAAT
Queue empty?? Impossible!
STA
ODATA
Output the character.
LDA
#41H
Set IEN and GO.
STA
OCS
RTS
* Interrupt
service routine
for output device.
osvc
LDX
#OQDT
Get another character to print.
JSR
QDEQ
BEQ
OQMT
Go to sleep if none left.
STA
ODATA
Else output the character.
LDA
#41H
Set IEN and GO.
OSVC8
STA
OCS
RTI
Return to interrupted program.
OQMT
CLR/
Turn off output device.
STA
OCS
LDA
#1
Put the output process to sleep...
STA
WAITOQ
...until there's a character in OQ.
RTI
Return to interrupted program.
Рис. 11.16. (Продолжение.)
жен быть по крайней мере один свободный байт, так чтобы мог быть возбужден процесс ввода, если он был в состоянии ожидания места для символа в очереди IQ.
Большинство команд подпрограммы INCHR входит в критическую секцию. Команды SETI и CLRI осуществляют управление разрядом I маски прерываний для разрешения или блокировки прерываний в критической секции. Рассмотрим последовательность событий, которая могла бы иметь место, если бы не были блокированы прерывания:
Очередь IQ заполнена, и процесс MAIN вызывает программу INCHR.
В программе INCHR вызывается подпрограмма QDEQ,. оставляющая место еще для одного символа в очереди IQ.
Появляется запрос на прерывание от устройства ввода^ и очередь IQ опять заполняется.
Появляется следующий запрос на прерывание от устройства ввода, и, так как очередь IQ заполнена, входной символ запоминается в SAVEIN, прерывание ввода блокируется и переменная WAITIQ устанавливается в 1.
Возобновляется выполнение программы INCHR, обнаруживается, что переменная WAITIQ установлена в 1.
Считая, что в это время в очереди IQ должно быть свободное место, программа INCHR пытается включить в очередь сохраненный символ, и появляется ошибка вследствие невыполнимости этого действия1).
Другая причина появления критических секций в подпрограмме INCHR состоит в том, что в ней использованы структуры данных IQ, общие с другим процессом — процессом ввода» Если прерывание ввода появляется в то время, когда в программе INCHR осуществляется выполнение подпрограммы QDEQ, оба процесса будут одновременно работать с одной структурой данных. Наличие или отсутствие ошибок, связанных с нарушением синхронизации, в этом случае, как уже пояснялось, зависит от работы подпрограмм QDEQ и QENQ. Блокировка прерываний в программе INCHR гарантирует, что каждый процесс получает монопольный доступ к очереди IQ.
Чтобы исключить все возможные ошибки синхронизации, может возникнуть желание блокировать прерывания на большое время. Однако это может привести к появлению других проблем. Например, если при переходе программы INCHR к следующей строке появляется команда CLRI, то прерывания будут
!) В этой ситуации можно предположить, что критические секции должны исчезнуть, если изменить поведение программы: вместо отказа работать разрешить ей попытаться включить символ в очередь IQ еще раз! При замене команды BEQ WHAAAT командой BEQ INCH4, если очередь заполнена, никаких неприятных последствий произойти не должно, и попытка операции возбуждения будет повторена позднее. Однако такое решение не является общим.
заблокированы для всей программы INCHR, даже если последняя находится в состоянии ожидания появления символа в очереди IQ. В этом случае подпрограмма INCHR навсегда остается в состоянии ожидания, так как прерывание ввода не может поместить символ в очередь.
Подпрограмма OUTCHR имеет такую же структуру, как и подпрограмма INCHR, и также содержит критические секции.
Отметим порядок, в котором устанавливаются и сбрасыва-
INTCNT
RMB
1
Counts number of times interrupts have been turned off; init to 1 in MAIN.
INTSON
DEC
INTCNT
Turn on interrupts
BGT
LVOFF
if turn-off count is <=0.
CLR
INTCNT
Clamp count at 0 just in case.
LVOFF *
CLRI
RTS
Allow interrupts.
INTSOFF
SETI
Turn off interrupts.
INC
RTS
INTCNT
Remember how many times.
Рис. 11.17. Подпрограммы вложенных блокировок и разрешений прерываний.
ются программные флажки в программах активации и обработки прерываний. Здесь полезно придерживаться двух рекомендаций:
Любое программное действие, показывающее, что прерывания разрешены (например, сброс WAITIQ), должно предшествовать соответствующему аппаратному действию (например, ICS: — 41Н) для того, чтобы не произошло прерывания, прежде чем программе будет сообщено о возможности появления этого события.
Любое программное действие, показывающее, что прерывания запрещены (например, установка WAITIQ), должно осуществляться после соответствующего аппаратного действия (например, ICS := 0) для того, чтобы не произошло прерывание в то время, когда программа «думает», что прерывания отключены.
Еще одна потенциальная возможность неработоспособности программы связана со способом, с помощью которого работа системы прерываний запрещается и вновь разрешается в подпрограммах, подобных INCHR и OUTCHR. Пусть в главной программе выполняются некоторые операции, критичные к синхронизации, при реализации которых в главной программе должны быть запрещены прерывания. Если же в это время в главной программе производится вызов подпрограмм INCHR или OUTCHR, то может оказаться, что система прерываний опять включена. Проблема состоит в том, что подпрограммы
INCHR и OUTCHR отключали систему прерываний и далее включали без проведения проверки, была ли она включена для работы с ними. Эта проблема может быть решена путем вызова вместо команд SEIT и CLRI двух подпрограмм INTSOFF и INTSON (рис. 11.17).
В этих подпрограммах для сохранения количества запрещенных прерываний счетчик используется так, что вложенные критические секции не разрешат прерывания до тех пор, пока не будет достигнут самый отдаленный вызов по метке INTSON.
Хотелось бы обратить внимание на то, что подпрограммы обработки очередей, представленные на рис. 9.21, являются полностью реентерабельными. Это означает, что в основном цикле вычислений, представленном на рис. 11.17, могут быть объявлены дополнительные очереди и может осуществляться их формирование с помощью подпрограмм QENQ и QDEQ. Ошибки не произойдет, даже если подпрограммы QENQ и QDEQ используются в мультипрограммном режиме в программах обработки прерывания.
Ошибки, вызванные нарушением синхронизации, которые возникают в параллельных процессах, могут оказаться неясными, особенно для неопытных программистов, которые могут поддаться желанию упрощенно трактовать их. К сожалению, появление ошибок такого рода зачастую трудно предсказать до того, как их заметили, и если даже эти ошибки удалось обнаружить, их трудно воспроизвести для анализа и последующего удаления. В приведенных ниже работах обсуждается, какие возможности предоставляют современные операционные системы и языки высокого уровня для параллельной обработки данных и какие накладывают ограничения.
УПРАЖНЕНИЯ
11.1. Программист заметил «несколько общих команд в подпрограммах STROUT и STRINT, представленных на рис. 11.2, и решил модифицировать подпрограмму STROUT:
STROUT TST
SBUSY
Выведена ли на дисплей предыдущая стро
BNE
STROUT
ка? Ожидание ее
LDA
#0FFH
Установка флажка SBUSY в 1
STA
SBUSY
BRA
STRINT1
Вывод первого символа
STRINT LDX
BUFPNT
Записать указатель буфера
INCX
Переход указателя к следующему символу
STRINT1STX
BUFPNT
Входная точка для первого символа
Поясните, почему эта программа не будет работать так, как она записана. Чтобы сделать программу действенной, поместите последовательность команд между командами STA SBUSY и BRA STRINT1.
Напишите программу, в которой используются прерывания для измерения и вывода на дисплей значения времени между последовательными попаданиями в цель при нажатии кнопок игрального автомата. Для этого необходимы три устройства ввода-вывода. Для генерирования прерываний через одинаковые интервалы времени должны использоваться часы реального времени. Программа обработки прерывания часов должна наращивать на 1 переменную TIME в памяти. Для выполнения точных попаданий, при которых вызываются прерывания (нажатие кнопки приводит к попаданию в цель), может применяться клавиатура или схожее с ней устройство ввода. Для сохранения значения времени последнего точного попадания можно использовать переменную LHIT. Дисплей или другое устройство отображения должно использоваться для вывода сообщения, показывающего значение времени, прошедшего после последнего попадания. Сообщение должно быть сформировано в виде строки и выведено на дисплей с помощью прерываний (см. рис. 11.2). Замечания. Точное попадание, которое произошло в момент вывода сообщения, не должно быть утеряно. Если во время вывода сообщения произошло два или большее число попаданий, то второе и все последующие попадания могут остаться незафиксированными. Если отсутствуют часы реального времени, их можно смоделировать в виде цикла в главной программе, который через ~ 1 мс (это определяется временем выполнения команд) добавляет 1 к значению переменной TIME. Потери времени на обработку прерываний в этом примере пренебрежимо малы.
Измените предыдущую программу так, чтобы во время вывода сообщения могло произойти несколько попаданий без утери информации. Для этой цели сформируйте очередь для значений времени, прошедшего после попадания. При каждом попадании в цель значение времени после попадания должно включаться в эту очередь. Существует два возможных программных способа извлечения данных из очереди и последующего их вывода. 1 — простой. В главной программе последовательно проверяется, работает ли программа вывода, и в том случае, если она бездействует, осуществляется проверка очереди на возможность извлечения из нее данных для вывода. 2 — более сложный. В главной программе имеется только одна команда, SELF JMP SELF после инициализации. Программы вывода данных и ведения игры рассматриваются как два различных процесса, связанных посредством очереди. Вывод находится в состоянии ожидания, если текущее сообщение выведено и очередь пуста. Программа ведения игры активирует вывод, когда посылает значение в пустую очередь. В обоих случаях следует изучить и пояснить возможность возникновения критических секций, когда в программах осуществляется доступ к общей структуре данных очереди. Для проверки работы программы при большом числе попаданий в цель используйте длинные сообщения (например, 70 символов) и медленное устройство вывода (30 символов в секунду).
Напишите реентерабельные программы вывода информации на дисплей, аналогичные представленной на рис. 11.2, которые могут использоваться несколькими идентичными дисплеями. При вызове каждой программе должен быть передан через регистр Y базовый адрес «таблицы конфигурации», в которой содержится информация, присущая каждому дисплею: а) адрес регистра DSPCS; б) BUFPNT, в) SBUSY.
Измените подпрограммы опроса (рис. 11.6) таким образом, чтобы таблица опроса содержала три 16-разрядных числа для каждого устройства. Третье число должно быть базовым адресом программной «таблицы конфигурации». В программе опроса это третье число должно загружаться в регистр Y перед программным переходом к своей программе обработки прерывания.
Поясните, каким образом может возникнуть взаимная блокировка процессов MainLoop и LineOut, представленных на рис. 11.10.
Переведите программу ReverseLine, представленную на рис. 11.10, в программу на языке ассемблера процессора, который вам хорошо знаком.
Поясните, как обеспечивается выполнение операторов WAKEUP и SLEEP, и покажите, где осуществляется блокировка прерываний для охраны критических секций. т w
Напишите обслуживающую программу для команды SWI3, используемой в микропроцессоре 6809, предполагая, что за указанной командой следует 1-байтовый код, который определяет вызов особой вспомогательной программы операционной системы. Обслуживающая программа должна «переделать» сохраненное в стековой памяти значение PC так, чтобы вспомогательная программа могла вернуться в исходную точку, используя команду RTI, и чтобы 1-байтовый код служил в качестве индекса в таблице начальных адресов вспомогательной программы.
Перепишите программу ввода с использованием ПДП (рис. 11.8) для использования двойного буферирования так, чтобы содержимое буфера ввода-вывода переписывалось в рабочий буфер по завершении каждой операции ПДП.
Перепишите программу ввода с использованием ПДП (рис. 11.8) для использования двойного* буферирования так, чтобы роли буфера ввода-вывода и рабочего буфера менялись по завершении каждой операции ПДП. Покажите, как главная программа производит выбор буфера.
Перепишите процесс, представленный на рис. 11.10, в виде подпрограмм и главной программы на языке ассемблера процессора, который хорошо вам известен. Прибегните к приему, аналогичному использованию сопрограмм для сохранения адресов возврата в приостановленный процесс. Напишите простую операционную систему, которая выполняет какое-либо действие (например, вызывает мигание лампочки на передней панели вашей ЭВМ), когда MainLoop находится в состоянии ожидания.
Какое требование является более строгим для подпрограмм: рекурсия или реентерабельность? Другими словами, ответьте на следующие два вопроса: 1) Гарантирована ли правильная работа всех рекурсивных подпрограмм, если они повторно используются другими процессами? 2) Гарантирована ли правильная работа реентерабельных подпрограмм, если они содержат вызовы самих себя? Приведите примеры.
Пусть оператор WAKEUP LineOut находится в конце процесса Main Loop (рис. 11.10) и просто планируется процесс LineOut, который должен быть скоро реализован, но не вызывает его немедленное выполнение. Процесс MainLoop может еще некоторое время работать. Какие проблемы могут возникнуть в связи с этим и как их можно решить?
Пусть модуль очереди разработан таким образом, что элементы извлекаются из очереди путем сдвига всех элементов в буфере памяти, как предполагалось в упр. 3.13. Каким образом такая организация очереди влияет на работу реентерабельных программ, функционирующих так, как указано в сноске к разд. 11.6.1?
Пусть мы используем программы для ввода и вывода информации с дисплея (рис. 11.16). Так как нельзя предотвратить очень быстрый вывод на экран всего текста, в программе ISVC может произойти потеря входной информации при блокировке прерываний. Какой программой можно заменить программу IQFUL, чтобы она предупреждала пользователя о надвигающейся опасности с помощью звонка на терминале?
Измените подпрограммы INTSOFF и INTSON (рис. 11.17) таким образом, чтобы подпрограмма INTSOFF проверяла значение разряда I в регистре СС для определения того, разрешены ли прерывания при очередном использовании подпрограммы INTSON. Нужна ли переменная INTCNT?
Напишите программу, которая моделировала бы «жизнь», организованную, как описано в упр. 7.5 и 10.6, управляя вводом-выводом с помощью прерываний. Программа должна содержать три процесса:
Процесс ввода. Принимает шестнадцатеричные символы (0—9, А—F), набираемые на клавиатуре и преобразуемые в код ASCII. Процесс ввода преобразует каждую цифру в двоичное число от 0 до 15 и помещает это число во входную очередь.
Процесс вывода. Извлекает символы на выходной очереди и осуществляет их вывод на дисплей.
Процесс главной программы. Инициализирует массив элементов «жизни», подпрограммы обработки очередей и ввода-вывода и следит за входной очередью. Когда процесс главной программы обнаруживает число во входной -очереди, он вычисляет количество поколений «жизни» и далее вызывает программу DISPLAY для индикации полученного результата. Программа DISPLAY осуществляет вывод каждого символа, помещая его в выходную очередь.
Если выходная очередь заполнена, прежде чем послать в очередь следующий символ, программа DISPLAY должна ожидать, пока символ из выходной очереди не будет выведен на дисплей. Для достижения максимальной эффективности выходная очередь должна иметь место для размещения такого же количества символов, что и на экране дисплея. Входная очередь должна иметь место только для пяти символов. Если символ набран на клавиатуре и входная очередь заполнена, программа должна исключить этот символ и поместить символы BEL («Звонок») в выходную очередь, для того чтобы предупредить пользователя, что входное воздействие утеряно. Проконтролируйте возможность возникновения ситуации взаимной блокировки в вашей программе.
ЛИТЕРАТУРА
Возможность совмещения вычислительных операций с операциями ввода- вывода была подмечена еще на ранней стадии развития вычислительной техники. В классической работе Беркса, Голдстайна и Неймана [Computer Structures: Readings and Examples, ed. by C. G. Bell, A. Newell, McGraw-Hill, 1971, pp. 117—118] указано, что: «для одновременной работы ЭВМ (центрального процессора) и устройства ввода-вывода требуется дополнительная память и возникает проблема синхронизации, вследствие этого при создании первых моделей ЭВМ эти вопросы не рассматривались».
Системы прерываний, снабженные необходимыми средствами синхронизации, используются в таких вычислительных машинах, как Univac 1103 (1953 г.), ТХ-2 лаборатории Линкольна (1957 г.) и Atlas Манчестерского университета [Computer Structures: Reading and Examples, McGraw-Hill, 1971, pp. 45, 274, 277]. Канал ПДП предусмотрен в ЭВМ Atlas и ЭВМ 7094 фирмы IBM (1960 г.) [Computer Structures: Reading and Examples, McGraw-Hill, 1971, pp. 523—524]. В ЭВМ Atlas используется понятие экстракодов, которое положено в основу современных систем программных прерываний и команд системного вызова.
Общее описание аппаратной реализации современных систем прерываний и каналов ПДП можно найти в следующих работах: Hayes J., Computer Architecture and Organization, McGraw-Hill, 1978; Hamacher, Vranesic, Zaky, Computer Organization, McGraw-Hill, 1978; Hill, Peterson, Digital Systems: Hardware Organization and Design, 2nd ed., Wiley, 1978. Наилучшим источником информации по системам прерываний и каналам ПДП для конкретных процессоров является техническая документация.
В большинстве книг по системному программированию обсуждается проблема управления процессом [Freeman Р., Software Systems Principles, SRA, 1975; Donovan J., Systems Programming, McGraw-Hill, 1972].
Понятие процессов и методы управления ими имеют большое значение при проектировании современных операционных систем. Подробно и доходчиво процессы и возможность их совместного выполнения рассмотрены в работе Хэнсена [Hansen В., Operating System Principles, Prentice-Hall, 1973]. Этот же автор предложил расширение языка Паскаль для структурного программирования операционных систем [The Programming Laguage Concurrent Pascal, IEEE Trans. Software Engr., SE-1, 2, 199—207 (June 1975)].
Вероятно, для программирования параллельных процессов наиболее широко применяется язык ПЛ/1 [Beech D., A Structural View of PL/1, Computing Surveys, 2, 1, 33—64]. В этом языке зарезервированное слово TASK указывает на то, что процедура может выполняться одновременно с вызовами программ, а переменные EVENT служат флажками завершения для синхронизации процессов. В процессе может использоваться оператор WAIT для передачи управления операционной системе, пока специальная переменная WAIT не будет установлена в 1.
Другим языком, применяемым в мультипроцессах, является расширенный Алгол фирмы Burroughs, который успешно использовался в течение десятилетия в ЭВМ. В6700 фирмы Burroughs. Интересная глава по параллельным алгоритмам, включающая вопросы, связанные с программами, асинхронными процессами с использованием расширенного Алгола, содержится в работе Ог- ganick Е. I., Forsythe A. I., Plummer R. Р., Programming Language Structures, Academic Press, 1978.
1212РАЗРАБОТКА ПРОГРАММРАЗРАБОТКА ПРОГРАММ
12
12
РАЗРАБОТКА ПРОГРАММ
РАЗРАБОТКА ПРОГРАММ
Начинающие программисты зачастую основное внимание уделяют тому этапу разработки программ, который на первый взгляд представляется им наиболее трудным, — записи алгоритма на языке программирования, или кодированию. Однако- кодирование является всего лишь одним из следующих этапов «жизненного цикла» программы: формулирование требований, предъявляемых к программе; составление спецификаций; проектирование; разработка документации; кодирование; тестирование и отладка; сопровождение программы.
Первые шесть этапов составляют традиционный «цикл разработки» программы. На этап кодирования приходится весьма малая доля цикла разработки (рис. 12.1). Еще более неожиданным является распределение трудозатрат между отдельными этапами жизненного цикла большой программы. Как показано- на рис. 12.2, этап сопровождения большой программы может вызвать большие трудозатраты, чем весь цикл разработки. Высокая стоимость сопровождения программ определяет необходимость такого построения цикла разработки, чтобы обеспечить возможность снижения объема работ по сопровождению программ. *
В этой главе изложены общие соображения, касающиеся всех этапов жизненного цикла программ, и поясняется связь этих этапов с программированием на языке ассемблера. Более детальное обсуждение способов рациональной организации процесса разработки программного обеспечения выходит за рамки данной книги, однако этому вопросу посвящены многочисленные литературные источники, список которых приведен в конце главы.
Цель данной главы заключается в том, чтобы помочь читателям, составлявшим лишь небольшие, «игрушечные» программы, оценить проблемы, возникающие при создании значительных по своим размерам программ, понять способы решения проблем и побудить таких читателей развивать правильные навыки программирования, прежде чем они убедятся в необходимости этого на собственных ошибках.
Рис. 12.1. Распределение трудозатрат по этапам разработки программного обеспечения.
Рис. 12.2. Распределение трудозатрат по этапам жизненного цикла большой программы.
ЭТАПЫ ЖИЗНЕННОГО ЦИКЛА
ПРОГРАММ
Этап формулирования требований присутствует в той или иной форме в любой деятельности, направленной на разработку какой-либо системы. На этом этапе вырабатываются требова-
ния, определяющие, какими характеристиками должно обладать проектируемое изделие. Большинство программистов не принимают участия в этой работе, поскольку она обычно осуществляется на уровне руководства проектом и связана с решением таких вопросов, как определение общего подхода к решению проблемы (целесообразно ли вообще привлекать для этого ЭВМ?), анализ людских и других ресурсов (будут ли при разработке данного проекта эффективно использоваться профессиональные возможности имеющегося персонала?), стоимость разработки (может ли организация позволить себе взяться за эту работу?) и сроки ее выполнения (будет ли разработка проекта осуществлена достаточно быстро для того, чтобы он стал полезным?).
Входы и выходы программы и их связи определяются на этапе составления внешних спецификаций. Например, спецификации для редактора текстов определяют формат текстовых файлов и содержат все команды редактирования и функции каждой из них. Однако внешние спецификации не дают описания того, как в программе будут реализованы эти функции; это является задачей проектирования.
На этапе проектирования определяется структура программы в соответствии с формулой Программа = Алгоритм + Структуры данных.
На данном этапе часто осуществляется декомпозиция общей задачи, которую необходимо решить, на отдельные фрагменты путем выделения некоторого набора взаимодействующих программных модулей высокого уровня. Такой подход требует дополнительных работ по проектированию и созданию внутренних спецификаций, так как каждый модуль и его взаимосвязи с другими модулями должны быть также определены, а затем разработана его внутренняя структура. В зависимости от сложности модуля может быть произведена его декомпозиция на подмодули.
Основная часть документации должна быть создана на этапах составления спецификаций и проектирования. Небольшая по объему и в то же время полная документация необходима для того, чтобы идеи, заложенные в проект разработки программы, могли быть донесены до разработчиков, пользователей и групп сопровождения программы. Документация необходима (но в меньшей степени) и на этапе кодирования с тем, чтобы пояснить детали кодирования программы.
На этапе кодирования проект записывается на языке программирования конкретной вычислительной системы. С началом кодирования проектирование, как правило, прекращается, что служит веским аргументом для того, чтобы не приступать к кодированию преждевременно. Анализ разработки программного обеспечения показал, что ошибки при проектировании соверша-
ются гораздо чаще, чем при кодировании, так что качественное проектирование является важнейшей предпосылкой успешного выполнения работ. Кроме того, на этапе проектирования имеется гораздо больше возможностей по сокращению размеров программы и улучшению ее эксплуатационных характеристик посредством совершенствования алгоритмов и структур данных, чем при кодировании и локальной оптимизации, когда приходится прибегать к различного рода ухищрениям.
Термин отладка обычно означает деятельность по устранению выявленных в программе ошибок. Тестированием же называют работу более сложную, в процессе которой проверяется не только работоспособность программы, но и ее соответствие внешним спецификациям. Для проведения тестирования необходимо составлять план тестирования. Основным содержанием этого плана являются наборы входных данных и ожидаемые результаты работы программы, которые позволяют проверить се правильность и пределы использования. Зачастую план тестирования разрабатывается как составная часть спецификаций с тем, чтобы исключить возможность проявления тенденции к составлению таких тестов, которые скрывали бы ошибки уже известной программы.
Для больших программ необходима организация их сопровождения во время эксплуатации, что вызвано двумя причинами. Во-первых, в программах, как правило, остаются ошибки, не выявленные при тестировании. Разумеется, увеличением объема тестирования при разработке можно понизить потребность в связанных с этим работах по сопровождению программ. А во-вторых, — чего избежать практически невозможно — пользователи в ходе эксплуатации программы настаивают на внесении в нее изменений и на ее дальнейшем совершенствовании. Затраты на сопровождение в значительной степени определяются качественным проведением этапов составления спецификаций, проектирования и разработки документации программы. Следовательно, необходимость сопровождения программы должна приниматься во внимание с самых первых этапов ее разработки.
СОСТАВЛЕНИЕ СПЕЦИФИКАЦИЙ И ПРОЕКТИРОВАНИЕ
Внешние спецификации программы полностью определяются результатами разработки требований к программе, и к их со ставлению приступают сразу же после того, как эти требовани сформулированы, в то время как внутренние спецификаци разрабатываются позже. В процессе разработки системы част оказывается необходимым осуществлять несколько итераци этапов составления спецификаций и проектирования (рис. 12.3
Это может потребоваться, когда при проектировании программы вскрываются неопределенности, противоречия или другие недостатки, свойственные внешним спецификациям. Однако в основном необходимость итераций вызывается применением метода нисходящего проектирования программ. После того как программа в соответствии с внешней спецификацией будет
Рис. 12.3. Цикл разработки программы.
спроектирована как комплекс взаимосвязанных модулей, каждый модуль и все его связи с другими модулями должны быть описаны с помощью внутренних спецификаций. Затем проектируется каждый модуль в отдельности. Этот процесс повторяется, если осуществляется декомпозиция модуля на подмодули.
Структура программы
, Разбиение программы на модули может быть проиллюстрировано и документировано блок-схемой, которая показывает взаимосвязи между модулями. Рассмотрим, например, следующие внешние спецификации.
Программа должна перемножать пары 16-битовых двоичных целых чисел для получения 32-битового произведения. Устройство ввода ЭВМ включает в себя 16 тумблеров и кнопку ввода.
Число поступает в ЭВМ при нажатии кнопки после того, как оно набрано с помощью тумблеров. Устройство вывода представляет собой табло, состоящее из 16 лампочек. После вычисления произведения программа использует эти лампочки для отображения множителя, множимого, 16 высших и 16 низших разрядов результата умножения. Каждое число высвечивается на табло в течение 10 с, интервал между отображением двух чисел должен составлять 1 с. Умножение осуществляется при
Рис. 12.4. Первоначальный вариант блок-схемы программы умножения.
каждом вводе пары чисел до тех пор, пока не поступит пара нулей.
Даже такую очень простую программу можно разбить на несколько модулей (рис. 12.4). Главная программа MAIN осуществляет умножение и вызов трех подпрограмм:
INSW — считывает 16-битовые числа с устройства ввода;
OUTDSP — производит отображение 16-битовых чисел на устройстве вывода;
DELAYN — осуществляет задержку на заданное время.
Применение нисходящего метода проектирования обеспечивает разбиение программы на небольшие части, с которыми можно работать по отдельности. Как только взаимосвязи между программами MAIN, INSW, OUTDSP и DELAYN определены,, их детальное проектирование и кодирование могут производиться более или менее независимо. При этом важно нс то, каким способом данный модуль выполняет свою функцию, а то, что он выполняет ее.
Детальное проектирование модуля
После того как определены структура программы и структуры ее данных, можно приступать к детальной проработке алгоритмов и наборов данных. На этой стадии проектирования удобно записывать алгоритмы на неформальном блочно-структурном языке, который допускает описание производимых действий на естественном языке. В примере начального варианта- проекта программы умножения (рис. 12.5) с помощью псевдоязыка дается описание программы и определяются функции трех подпрограмм. Конечно, для составления такой простой программы нет необходимости выполнять большую предвари*
тельную работу по ее проектированию. Однако на этом примере легко продемонстрировать те приемы и методы, которые являются необходимыми при разработке больших программ.
PROGRAM Multiply;
VAR X, Y — input numbers (each a 16-bit 2’s-comp integer)
PH, PL — high-order and low-order product
(PH and PL form a 32-bit 2*s-complement integer)
FUNCTION INSW : word;
This function reads a 16-bit word from the input switches and returns the 16-bit value to the caller.
'PROCEDURE OUTDSP (num : word) ;
This procedure displays a 16-bit word in the lights.
PROCEDURE DELAYN (N : unsigned integer);
This procedure delays for N seconds, where N is a 16-bit value interpreted as an unsigned integer (0 <= N <= 65535) (Reminder: Don’t forget to check for N=0.)
BEGIN Main program
Read X and Y from the switches.
'.WHILE (XO0) OR <Y<>0) DO
BEGIN
Compute the double-length product PH,PL.
Display X, Y, PH, and PL with appropriate delays.
Read X and Y from the switches.
END;
END.
Рис. 12.5. Первоначальный вариант проекта программы умножения.
Итерации этапа проектирования
После завершения первоначального проектирования можно сделать вывод о необходимости дальнейшей декомпозиции. Например, может выявиться некоторая последовательность действий, которая достаточно часто повторяется, так что целесообразно выделить ее в отдельную процедуру.
PROCEDURE DISPLAY (параметр: слово);
Процедура осуществляет отображение числа на табло устройства вывода с задержкой, определяемой спецификацией:
BEGIN OUTDSP (параметр); DELAYN(10); OUTDSP(0); DELAYN(1) END;
Может также оказаться, что в данном вычислительном устройстве не реализована операция умножения, тогда придется воспользоваться процедурой перемножения 16-битовых чисел из библиотеки программ. Необходимость создания новой процедуры может быть обусловлена также отсутствием аппаратно- реализованного таймера для выполнения задержек. Таким образом, может возникнуть необходимость в разработке программного тайме ри — процедуры DLY1S, которая осуществляет задержку величиной 1 с; подпрограмму DELAYN следует спро-
ектировать так, чтобы для выполнения задержки N секунд она вызывала процедуру DLY1S N раз. Все эти изменения найдут отражение в структуре программы (рис. 12.6). Теперь можно
Рис. 12.6. Уточненный вариант блок-схемы программы умножения.
уточнить алгоритм, так что тело главной программы будет иметь вид
BEGIN Программа Main
X : = INSW; Y := INSW; (Чтение X и У)
WHILE (ХОО) OR (YOO) DO
BEGIN
PH,PL := X*Y; (Вызов MULTI6 для вычисления произведения двойной длины)
DISPLAY(X); DISPLAY(Y); (Вывод X и Y)
DISPLAY(PH); DISPLAY(PL); (Вывод произведения) X := INSW; Y := INSW (Чтение X и Y)
END;
END;
Некоторые нерешенные вопросы проектирования могут выявиться лишь на этапах кодирования или даже тестирования, что вызовет проведение новой итерации проектирования, как показано штриховой линией на рис. 12.3, и составления спецификаций для появившихся на этой итерации модулей программы. Например, особенности работы кнопки ввода могут оказаться такими, что подпрограмма INSW должна начинать работу по считыванию числа с наборного поля с задержкой 10 мс. Следовательно, необходимо включить в программу новую процедуру DLY10M, обеспечивающую задержку 10 мс, при этом может оказаться удобным получать задержку 1 с в процедуре DLY1S посредством стократного вызова процедуры DLY10M. В резуль-
тате структура программы приобретет окончательный вид (рис. 12.7).
Большие блок-схемы также могут быть разбиты на несколько схем в соответствии с разбиением программы на модули. Схема высшего уровня описывает взаимосвязи между модуля» ми высшего уровня, внутренние структуры которых описывают-
Рис. 12.7. Окончательный вариант блок-схемыРис. 12.7. Окончательный вариант блок-схемыпрограммы умножения.программы умножения.
Рис. 12.7. Окончательный вариант блок-схемы
Рис. 12.7. Окончательный вариант блок-схемы
программы умножения.
программы умножения.
ся схемами более низкого уровня. Такое построение блок-схем отображает блочную структуру языка Паскаль, а также принципы нисходящей разработки программ на любом языке программирования.
Наборы данных
До сих пор мы рассматривали связи между главной программой и ее подпрограммами. Однако существенной частью программы являются и наборы данных. В каждом случае, когда набор данных используется двумя или большим числом подпрограмм, становится необходимым указать, какие подпрограммы и каким образом могут получить к нему доступ. В блок-схеме модуль данных может быть изображен, как и любой другой модуль, а доступ подпрограмм к этим данным можно показать с помощью штриховых линий. Примером использования таких обозначений является блок-схема программы таймера (упр. 11.3), приведенная на рис. 12.8.
Блок-схемы и описания алгоритмов и наборов данных на высоком уровне иерархии позволяют программисту контролировать развитие структуры программы при проектировании, однако спецификации модулей должны содержать гораздо более
Рис. 12.8. Блок-схема программы таймера, описанного в упр. 11.3.
детальную информацию. В следующем разделе подробно изложено, какую информацию и в каком виде необходимо включать в комментарий для каждого модуля.
ДОКУМЕНТАЦИЯ
Разработка документации должна производиться не на каком-то одном, фиксированном этапе, а вестись на протяжении всего цикла разработки в соответствии с выполняемыми этапами работы. Может быть, ваш опыт документирования сводится лишь к написанию комментария к написанной на языке высокого уровня или на языке ассемблера программе, однако необходимо ясно представлять, что наиболее важная программная документация разрабатывается задолго до начала этапа кодирования— еще на этапах составления спецификаций и проектирования.
В промышленности все более широкое распространение получает практика выпуска самодокумонтирующихся программ, т. е. программ, оформленных таким образом, что вся документация, включая спецификацию и алгоритм, содержится в том же файле, что и сам текст программы. Разработка самодокументирующихся программ обладает рядом достоинств по сравнению с хранением рукописной или отпечатанной документации отдельно от самой программы:
проще установить соответствие документации тексту программы;
можно установить эффективные процедуры сопровождения текста программы и документации на вычислительной системе, с помощью которой осуществляется разработка программы;
удобнее вносить соответствующие изменения в документацию при изменении алгоритма или текста программы. В противном случае документация, как правило, выпускается со значительным запаздыванием по отношению к проектированию;
в процессе сопровождения и корректировки вся документация доступна в той же степени, что и текст программы, и ей не уготована судьба быть утерянной при бумажных перетасовках.
Основным недостатком самодокументирующихся программ является увеличение размеров текстовых файлов, с которыми должна работать вычислительная система разработки программ. С учетом файлов программ, обеспечивающих разработку программного продукта, при таком оформлении программы потребуется от 200 до 1000 байт внешней памяти на каждый байт объектного кода составляемой программы1). Так, при разработке прикладной программы 50К байт для микропроцессора необходимо использование мини-ЭВМ или даже ЭВМ среднего класса, обладающей внешней памятью размером ~50М байт.
Комментарии
Внешние спецификации для программы в целом полностью определяются требованиями, предъявляемыми к программе. По мере проектирования программы для каждого модуля составляются внутренние спецификации. Каждая из спецификаций может быть документирована в комментариях к соответствующему модулю. Комментарии являются частью исходного текста программы для каждого модуля, таким образом составляются са- модокументирующиеся программы.
]) В качестве примера рассмотрим модуль формирования очереди, приведенный на рис. 9.21. Объем кода программы составляет ~60 байт, а текст, содержащий необходимую документацию, имеет объем 4800 символов кода ASCII, т. е. коэффициент увеличения объема памяти равен 80. Если в комментарии встречаются еще и хорошенькие рамочки, которые имеют обыкновение изображать некоторые программисты, величина коэффициента возрастет до 100 и выше. Приняв во внимание память, необходимую для хранения блок- схем, их описаний, пояснений, и удвоив полученное значение, чтобы учесть программы разработки, вычислим минимальное значение коэффициента, которое составит 300. К этому необходимо добавить память для хранения объектных модулей и временных файлов, создаваемых при трансляции, сборке и редактировании. Если в системе хранится несколько копий или версий программы, коэффициент памяти возрастет до 1000 или выше. Очевидно, что для эффективного управления всеми файлами, необходимыми для создания большой прогпаммы, требуется мощная вычислительная система разработки.
Программа содержит программные модули и модули данных. Программные модули включают процедуры и локальные наборы данных. Отдельные модули данных вводятся только для тех наборов данных, которыми пользуются несколько программных модулей.
Комментарии к программным модулям
На рис. 12.9 показана структура файла программного модуля, содержащего несколько процедур. Модуль начинается с краткого описания выполняемых им функций. Затем определяются глобальные и локальные данные, используемые процедурами, и объявляются локальные данные (если набор данных
Рис. 12.9. Структура текстового файла программного модуля.
используется только одной процедурой, он может объявляться в теле самой процедуры). Набор данных является локальным по отношению к программному модулю, если он используется только данным модулем или его подмодулями, в противном случае набор данных является глобальным. Далее в тексте модуля следуют комментарий к модулю и исходные коды входящих в него процедур.
Примеры программных модулей с комментариями даны на рис. 9.21 и 10.16. Краткая характеристика модуля и описание наборов данных могут составляться в произвольной форме, но их присутствие в тексте модуля является обязательным. Необ-
ходимо подчеркнуть, что оба указанных примера демонстрируют возможность включения в описания данных рисунков. Такая возможность появляется при использовании современных редакторов текста, ориентированных на работу с дисплеями.
Комментарии к процедурам должны содержать следующие сведения:
имя процедуры и выполняемые ею функции, сформулированные возможно более кратко;
описание процедуры, в котором, если это необходимо, дается более подробное изложение основных данных о процедуре. В него целесообразно включать назначение, условия применения, выполняемые функции, используемые алгоритмы, ожидаемую область значений входных и выходных данных;
входные и выходные параметры процедуры и способ их передачи (как входным, так и выходным параметрам целесообразно присваивать идентификаторы, даже если они передаются с помощью регистров);
описание всех глобальных данных, используемых процедурой, за исключением уже описанных в комментарии к модулю;
описание и объявление всех локальных данных, за исключением уже определенных в комментарии к модулю1*;
функцию процедуры — порядок использования входных данных для получения результатов работы процедуры, учет влияния, оказываемого процедурой на значения локальных и глобальных данных;
перечисление используемых регистров, содержимое которых изменяется процедурой, при написании программы на языке ассемблера. Если такой перечень не приведен, следует полагать, что изменяется значение всех регистров и известными можно считать значения только тех регистров, в которых передаются выходные параметры процедуры;
порядок вызова процедуры с указанием примера записи вызова;
возможные ограничения, приемы и прочая информация, позволяющая программисту понять, скорректировать или усовершенствовать процедуру.
Комментарии к модулям данных
Выделение данных в отдельные модули может потребоваться для тех наборов данных, которые используются двумя или боль-
о В описание локальных данных не обязательно включать переменные. Но все переменные и наборы данных, значения которых должны сохраняться между последовательными вызовами процедуры, должны быть описаны. Например, в программе на рис. 11.2 SBUSY является глобальной переменной, используемой как главной программой, так и процедурами, a BUFPNT — локальная переменная, значение которой должно сохраняться между последовательными вызовами процедур.
шим количеством программных модулей. Соответствующий пример показан на рис. 12.8. Другим примером может служить выделение буфера текстов в программе редактора текста, к которому должны иметь доступ модули ввода, вывода и редактирующий модуль. Блок-схема программы приведена на рис. 12.10. Комментарии к модулям данных должны давать точ-
Рис. 12.10. Блок-схема программы редактора текста.
ное описание структур данных; при этом возможно использование графических средств или удобочитаемого псевдоязыка для иллюстрации схем, таблиц и указателей (см. рис. 9.21 и 10.16),
Рис. 12.11. Фрагмент блок-схемы программы на ассемблере.
На практике часто бывает нежелательна организация отдельных модулей данных. Вместо этого создается некоторый комплекс процедур, имеющих доступ к определенным данным и осуществляющих их обработку. Рассмотрим, например, таблицу символов, обрабатываемую ассемблером. Программа ассемблера должна выполнять с ней целый ряд операций, таких, как включение в таблицу новых символов, поиск находящихся в ней элементов, сортировка содержимого таблицы. При описании таблицы символов как глобального набора данных главная программа сможет ее обрабатывать лишь в том случае, если описание структуры таблицы войдет в состав главной программы.
Имеется, однако, более рациональный способ организации программы (рис. 12.11), при котором таблица символов включена в состав модуля SYMBOL ROUTINES, располагающего несколькими процедурами, способными выполнить требуемые функции. Поскольку главная программа при этом получает доступ к таблице символов лишь посредством обращения к данному модулю, становится возможным исключить необходимость переработки главной программы при изменении структуры таблицы (например, при переходе от таблицы с последовательным доступом к связанному списку). Детальная информация о структуре таблицы символов при этом не нужна главной программе и размещена в процедуре SYMBOL ROUTINES.
Принадлежность глобальных наборов данных
Набор данных принадлежит тому модулю, в котором он объявляется, или, другими словами, модулю, в котором для него отводится память. Данные являются локальными по отношению к тому модулю, в котором они объявляются, и глобальными по отношению к модулям нижних уровней. Таким образом, программный модуль нижнего уровня может не иметь локальных наборов данных, а его процедуры будут в этом случае оперировать глобальными данными, объявленными на более высоком уровне. В качестве примера можно привести модуль формирования очередей (рис. 9.21). Он не содержит в себе никаких очередей, но может обрабатывать очереди определенной структуры.
Описание глобальных наборов данных следует включать не только в модули, которым они принадлежат, но и в модули, в которых они используются. Однако перепечатывать одни и те же длинные описания данных для каждого использующего их модуля весьма утомительно. Более того, практически невозможно обеспечить полное согласование между собой вариантов описаний, размещенных в разных местах программы. Для устранения этих трудностей в развитых системах разработки программного обеспечения имеется процедура «Включить». В такой системе описание каждого набора глобальных данных оформляется в виде отдельного файла. Программисту достаточно вставить в комментарий фразу «4|=Включить имя файла», чтобы ввести в него описание структуры данных. Процедура «Включить» автоматически переписывает текст описания из соответствующего файла в комментарий к модулю. Таким образом, все изменения в описании набора данных автоматически вводятся во все комментарии, где эти описания присутствуют. Вместе с тем необходимо подчёркнуть, что за внесение всех требуемых изменений в текст программы ответствен программист..
Документирование текста программы
Керниген и Плоджер — авторы небольшой интересной книги «Элементы стиля программирования» (Kernighan, Plauger, The Elements of Programming Style) приводят хорошие советы по написанию самодокументирующихся программ на языках высокого уровня:
«Лучшей документацией программы является ее четкая структура. Значительно облегчается понимание программы и в том случае, когда текст программы записан в соответствии с определенными правилами, содержит легковоспринимаемые идентификаторы и метки и небольшое число поясняющих комментариев. Блок-схемы и описания программ играют второстепенную роль. Единственным достойным доверия документом является сам текст программы. Это объясняется тем, что с увеличением количества документов увеличивается и вероятность появления в них ошибок. Если в тексте программы имеется ошибка, то и красиво оформленные блок-схемы, и детальные описания бесполезны. Только прочитав текст программы, программист может знать наверняка, какие функции в ней реализованы».
Однако этим рекомендациям довольно трудно следовать при программировании на языке ассемблера. Основным назначением комментариев в тексте программы на ассемблере является всего лишь установление соответствия текста программы и описания производимых действий, выполняемого на языке более высокого уровня. ,
В данной книге использованы два способа документирования программ на ассемблере. При первом способе, который является наиболее распространенным, для пояснения отдельных команд или их небольших групп употребляются предложения и фразы, написанные на естественном языке. В случае использования такого способа нет необходимости комментировать каждую строку программы при условии, конечно, что изучающий программу знает этот язык программирования. При этом желательно избегать употребления таких комментариев, как
. ADDA #1 Увеличить А на 1
а давать более содержательные пояснения, например: ADDA #1 Увеличить счетчик циклов
В то же время, если цель увеличения А очевидна, комментарии вообще не нужны. Как подчеркивают авторы вышеупомянутой работы^ «комментарии, не несущие никакой новой информации по сравнению с текстом программы, а лишь повторяющие его, излишни». .
При втором способе документирования используется язык программирования высокого уровня для пояснения фрагментов текста программы. Для этого подходит любой язык, близкий к естественному, либо псевдоязык. Примеры использования языка Паскаль (с некоторыми отступлениями) приведены на рис. 7.7, 7.13 и 8.3. Опытные программисты, работающие с ассемблером, разрабатывают первоначальный проект программы, пользуясь именно такими языками. Этим обеспечивается очень простой способ хранения проекта программы и явным образом устанавливается соответствие между проектом и текстом программы. При написании комментария основное внимание должно быть обращено не на синтаксическую правильность фраз языка высокого уровня, а на обеспечение легкости их понимания и точность описания действий, определяемых текстом программы.
КОДИРОВАНИЕ
Правила кодирования
Кодирование является, пожалуй, наиболее изученным из всех этапов разработки программного обеспечения. Действительно, кодирование хорошо разработанного проекта программы можно отнести к чисто технической работе. Чрезвычайно важным для правильной организации разработки программ является понимание того, что хорошие результаты при кодировании достигаются в первую очередь за счет высокого качества проекта программы, а не за счет искусного кодирования.
Керниген и Плоджер в своей книге, о которой мы упоминали выше, проанализировали достоинства и недостатки десятков программ. На основе этого анализа они составили свод правил, которыми следует руководствоваться при разработке программного обеспечения. Многие из правил имеют непосредственное отношение к кодированию:
Стремитесь к простой, а не к сложной программе.
Четко и ясно выражайте свои мысли.
Возлагайте рутинную работу на ЭВМ.
Сначала записывайте программу на легковоспринимаемом псевдоязыке, а затем переводите ее на требуемый язык программирования.
Не останавливайтесь на первом варианте программы.
Выбирайте такой способ представления данных, который упрощает программу.
Не исправляйте неудачный участок программы — перепишите его заново.
Убедитесь, что входные данные соответствуют принятым ограничениям.
Обеспечьте распознавание неверно заданных входных данных и, если это возможно, их исправление.
Стремитесь к тому, чтобы инициализация переменных проводилась прежде, чем они будут использованы.
Обратите внимание на правильность передачи управления в том случае, если в результате сравнения двух выражений выявилось их равенство.
Обеспечьте возможность нормального завершения программы в случае неверного задания исходных данных.
Стремитесь в первую очередь к получению правильной программы и только потом к сокращению времени ее выполнения,
Отдавайте предпочтение ясности программы, а не скорости ее выполнения.
Обеспечьте сохранение правильности программы при ее оптимизации.
Не тратьте зря времени на изменение кода программы для сокращения времени ее работы — ищите лучший алгоритм,
Обеспечьте соответствие комментария тексту программы,
Комментарий должен не повторять текст программы, а пояснять смысл выполняемых действий.
Не пытайтесь с помощью комментария сделать понятной плохую программу — перепишите ее заново,
Идентификаторы переменных и метки должны нести: смысловую нагрузку.
Примеры, приведенные в обсуждаемой нами книге, ярко демонстрируют важность следования этим правилам при разработке программного обеспечения. Кодирование всех приведенных в данной книге программ как на языке ассемблера, так и на Паскале выполнялось в соответствии с перечисленными правилами, хотя в ряде случаев имелись и некоторые отклонения, Читателю предлагается в качестве упражнения обнаружить дол пущенные отклонения.
Наиболее важными среди перечисленных правил являются те, которые рекомендуют добиваться простоты программ и избегать всевозможных программистских ухищрений. К последним обычно стремится большинство программистов, что связано с разными причинами: делает работу более интересной, дает возможность показать, насколько сложной и важной является работа программиста, а в ряде случаев служит гарантией сохранения работы для программиста, так как кроме него никто не сможет осуществлять сопровождения программы.
Однако бывают случаи, когда усложнение кода выполняется на законном основании, например если надо добиться максимального сокращения времени работы программы или занимаемой ею памяти. Необходимость этого может быть вызвана многими причинами — от плохого планирования до стремления по-
лучить экономический эффект. При выпуске вычислительных систем или программ тиражами в сотни тысяч даже значительные дополнительные затраты на их разработку могут окупаться и давать существенную прибыль. В связи с этим ниже будут рассмотрены некоторые приемы, повышающие эффективность программ, с указанием их недостатков. Следует еще раз подчеркнуть, что эффективность программы определяется на этапе ее проектирования, задолго до начала кодирования программы).
Приемы кодирования
Большинство приемов кодирования служит для сокращения времени работы программы. Как правило, основная часть времени затрачивается программой на выполнение циклов, поэтому повышение эффективности циклов программы обеспечит улучшение ее характеристик в большей степени, чем совершенствование любой другой ее части. Исключение даже нескольких команд в коротких циклах дает существенный выигрыш во времени их выполнения.
Рассмотрим некоторые способы повышения скорости выполнения циклов:
Исключение излишних команд.
Оптимальное использование регистров.
Вынесение безусловных переходов в программе за пределы цикла.
Проверка особых случаев.
Вынесение операций с результатами, не зависящими от работы цикла, за его пределы.
Объединение проверок многих условий в одну проверку.
Если программист механически переводит операторы языка высокого уровня в команды ассемблера, появление излишних команд неизбежно. Рассмотрим, например, следующий условный оператор, определяющий максимальное число из двух целых чисел i и j:
IF i>j THEN max := i ELSE max : = j;
Этот оператор может быть записан на языке ассемблера в виде простой эквивалентной последовательности команд (см. разд. 2.8):
LDA
I
СМРА
J
BGT
LABELA
LDA
J
STA
MAX
BRA
LABELB
LABELA LDA
I
STA
MAX
LABELB ...
Однако можно написать более короткую и выполняемую за меньшее время совокупность команд, которая воспринимается не труднее, а, возможно, даже и легче, чем предыдущая:
LDA I СМРА J BGT LABELA LDA J LABELA STA МАХ
Другим широко распространенным способом совершенствования кода является оптимальное использование регистров в теле цикла. Поскольку команды, выполняющие действия над регистрами, обычно короче, а время их выполнения меньше, чем команд, работающих с памятью, наиболее часто используемые в цикле переменные и константы целесообразно хранить в регистрах.
Команды безусловных переходов, которые встречаются в циклах, зачастую могут быть вынесены за их пределы, что сокращает число команд в циклах. Сравним, например, вариант цикла, приведенный на рис. 7.18, и другую его версию, данную ниже:
SCOREI LDX
HEAD
Указатель первого элемента списка
BRA
LOOPIN
Вход в цикл
SCLOOP CLRA
Инициализация
STA
SCOREI (X)
Сброс трех оценок
STA
SCORE2(X)
STA
SCORE3(X)
LDX
LINK(X)
Выборка адреса следующего элемента
LOOPIN CMPX
*NULL
Конец списка?
BNE
SCLOOP
Нет, продолжить выполнение цикла
RTS
Да, цикл завершен
В последнем варианте по сравнению с предыдущим изменена структура за счет замены безусловного перехода в начало цикла условным переходом, который, конечно, должен в нем присутствовать.
Имеются еще две возможности совершенствования рассмотренного цикла. Первая определяется удачной комбинацией команд и данных. Команда CMP4HNULL является излишней, так как величина NULL определена равной нулю. Разряд Z уже установлен в 1, если X было присвоено нулевое значение. Поэтому программу можно привести к следующему виду:
SCOREI LDX
HEAD
Указатель первого элемента списка
BRA
LOOPIN
Вход в цикл
SCLOOP CLRA
Инициализация
STA
SCOREI (X)
Сброс трех оценок
STA
SCORE2(X)
STA .
SCORE3(X)
LDX
LINK(X)
Выборка адреса следующего элемента
LOOPIN BNE
SCLOOP
Продолжить, если список не окончился
RTS
Цикл завершен
Однако в дальнейшем будет показано, что этот метод чреват опасными последствиями и не может быть рекомендован к использованию.
Другим способом улучшения программы является вынесение из цикла команд, результат выполнения которых не зависит от параметров цикла. Как правило, эти команды осуществляют присвоение переменной какого-либо постоянного значения. Это действие достаточно сделать один раз перед началом цикла, а не повторять его столько раз, сколько итераций будет сделано в цикле. В рассматриваемом примере команда CLRA может исполняться лишь однажды:
SCOREI LDX
HEAD
Указатель первого элемента списка
CLRA
Инициализация
BRA
LOOPIN
Вход в цикл
SCLOOP STA
SCOREI (X)
Сброс трех оценок
STA
SCORE2(X)
STA
SCORE3(X)
LDX
LINK(X)
Выборка адреса следующего элемента
LOOPIN BNE
SCLOOP
Продолжить, если список не окончен
RTS
Цикл завершен
Все произведенные усовершенствования привели, однако, к внесению в программу ошибки, так как командой CLRA подготавливается установка значения Z в 1 для дальнейшей проверки командой BNE. Ошибка возникла в результате недостаточно внимательного анализа имеющего здесь место особого случая.
Даже если устранить ошибку, поменяв местами команды CLRA и LDX HEAD, проблема решена не будет, так как предпосылки для внесения ошибок в программу остаются. Напри-
мер, если потребуется изменить значение NULL с 0 на —1, программа не будет работать, и для ее восстановления необходимо снова включить в текст программы команду СМРХ. Более того, ни из текста программы, ни из комментария не следует, что значение NULL существенно для работы программы.
Пожалуй, наиболее рациональным способом сокращения времени работы цикла является переход от выполнения многих проверок к одной. Рассмотрим, например, следующую процедуру, осуществляющую поиск в буфере заданного символа:
Найти в буфере первый же символ, совпадающий с символом, переданным процедуре в аккумуляторе А. Выходные параметры процедуры: Z=l, если такой символ не найден; Z=0, X равен значению адреса символа в буфере, если он обнаружен
SEARCH
LDX
^BUFFER
Указатель начала буфера
SLOOP
CMPA
(X) +
Равенство выполняется?
BEQ
OUT
Выход, если выполняется
CMPX
#BUFEND
Конец буфера?
BNE
SLOOP
Нет, продолжать перебор
RTS
Равных величин нет, выход при Z=1
OUT
ADDX
*-l
Присвоить X значение адреса найденного
символа
CLRZ
Установить Z равным 0
RTS
Конец
BUFFER
RMB
1000
Выделить 1000 байт для буфера
BUFEND
EQU
♦
Адрес, следующий за буфером
На первый взгляд может показаться, что программа написана наилучшим образом. Осуществлено даже автоматическое увеличение адреса, что позволило сократить количество команд в цикле на одну. Обе проверки условий, имеющиеся в цикле, кажутся совершенно необходимыми: одна для поиска символа, другая для выхода из цикла, если символ не найден. Однако из тела цикла можно исключить одну проверку, а по завершении цикла установить, был в цикле найден искомый символ или нет. В этом случае скорость выполнения цикла удвоится:
SEARCH LDX STASLOOP СМРА BNE ADDX СМРХ RTSSEARCH LDX STASLOOP СМРА BNE ADDX СМРХ RTS#BUFFER EXTRA(Х) +SLOOP*-1#EXTRA#BUFFER EXTRA(Х) +SLOOP*-1#EXTRABUFFER RMB1000EXTRA RMB1BUFFER RMB1000EXTRA RMB1Указатель начала буфераЗапись искомой величины для контроля Совпадение произошло?Нет, продолжить выполнение циклаДа, определить адрес найденного символа Проверен весь буфер?Конец; Z = 0, если символ найден; Z=1 в противном случаеВыделить буфер объемом 1000 байтВыделить 1 байт для завершения циклаУказатель начала буфераЗапись искомой величины для контроля Совпадение произошло?Нет, продолжить выполнение циклаДа, определить адрес найденного символа Проверен весь буфер?Конец; Z = 0, если символ найден; Z=1 в противном случаеВыделить буфер объемом 1000 байтВыделить 1 байт для завершения цикла
SEARCH LDX STA
SLOOP СМРА BNE ADDX СМРХ RTS
SEARCH LDX STA
SLOOP СМРА BNE ADDX СМРХ RTS
#BUFFER EXTRA
(Х) +
SLOOP
*-1
#EXTRA
#BUFFER EXTRA
(Х) +
SLOOP
*-1
#EXTRA
BUFFER RMB1000
EXTRA RMB1
BUFFER RMB1000
EXTRA RMB1
Указатель начала буфера
Запись искомой величины для контроля Совпадение произошло?
Нет, продолжить выполнение цикла
Да, определить адрес найденного символа Проверен весь буфер?
Конец; Z = 0, если символ найден; Z=1 в противном случае
Выделить буфер объемом 1000 байт
Выделить 1 байт для завершения цикла
Указатель начала буфера
Запись искомой величины для контроля Совпадение произошло?
Нет, продолжить выполнение цикла
Да, определить адрес найденного символа Проверен весь буфер?
Конец; Z = 0, если символ найден; Z=1 в противном случае
Выделить буфер объемом 1000 байт
Выделить 1 байт для завершения цикла
Многие компиляторы языков высокого уровня автоматически* осуществляют оптимизацию кода, например исключение излишних команд и перенос команд, независимых от цикла, за его» пределы.
В заключение еще раз отметим, что наиболее важным правилом, которому должны следовать программисты независимо* от языка программирования, с которым они работают, является: «Стремитесь в первую очередь к получению правильной программы и только потом — к сокращению времени ее выполнения».
ТЕСТИРОВАНИЕ И ОТЛАДКА
Тестирование и отладка предназначены обеспечить соответствие программы ее спецификациям. Цель тестирования заключается в выявлении имеющихся в программе ошибок. Цель отладки состоит в установлении причин, вызывающих ошибки,, выявленные при тестировании, и в их устранении. Как следует из рис. 12.1, тестирование и отладка представляют собой наиболее трудоемкий этап цикла разработки программного обеспечения. '
Порядок тестирования и отладки
Многие начинающие программисты, даже понимающие важность этапа проектирования и разрабатывающие хорошие проекты программ, при выполнении последующих этапов работы больше полагаются на удачу, чем на правильную методику работы. Они кодируют полностью всю программу, а затем прогоняют ее, надеясь на то, что им повезет и ошибок в программе не окажется. И хотя каждый программист не теряет надежды на то, что большая программа начнет правильно работать с первого же прогона, многим из них удается испытать радость такого события лишь однажды за всю свою практическую деятельность. Обычно при таком «отчаянном» первом испытании программа либо вообще не работает, либо работает неверно.
Наиболее правильный метод разработки большой программы заключается в последовательном кодировании, тестировании и отладке небольших частей программы.
Существуют два основных подхода к разработке программного обеспечения:
Метод восходящей разработки. В первую очередь кодируются, тестируются и отлаживаются модули самого нижнего уровня. Эти модули, работоспособность которых уже обеспечена, включаются в разрабатываемые модули более высокого уровня. Например, при создании программы умножения, рассмотренной в разд. 12.2, можно закодировать и тестировать 31*
подпрограммы INSW, OUTSW и DELAYN до начала кодирования главной программы. Для тестирования каждой из подпрограмм требуется разработать простую управляющую программу.
Метод нисходящей разработки. Вначале кодируются, тестируются и отлаживаются модули самого верхнего уровня. Для проведения тестирования вместо еще не закодированных модулей нижнего уровня должны быть использованы заглушки, которые соответствуют спецификациям входа и выхода замещаемых модулей, но гораздо проще их по выполняемым функциям. Например, при разработке программы умножения можно вначале провести тестирование входа, выхода и управляющих функций главной программы MAIN с использованием заглушки вместо процедуры умножения. Заглушка может, например, передавать главной программе по адресу результата (в слова РН и PL) вместо произведения X*Y значения сомножителейг X и^ Y.
Оба метода обладают как достоинствами, так и недостатками. При восходящем методе разработки принципиальные ошибки в проекте модулей верхнего уровня будут выявлены лишь на заключительной стадии работ. При использовании нисходящего метода разработки практически до се завершения остаются неизвестными размеры программы и ее эксплуатационные характеристики, так как они определяются в основном модулями нижнего уровня, а следовательно, остаются нерешенными и все связанные с ними вопросы. Применение обоих методов связано с разработкой дополнительных программ для проведения тестирования. На практике обычно наилучшие результаты дает сочетание обоих методов, при котором вначале создаются модули и верхних, и критичных нижних уровней с использованием заглушек для имитации менее существенных для характеристик программы модулей, разрабатываемых впоследствии.
Необходимо подчеркнуть, что тестирование и отладка также состоят из нескольких этапов, особенно при разработке больших программ. Около половины всех трудозатрат на тестирование и отладку приходится на проверку соответствия отдельных модулей внутренним спецификациям. Этот этап иногда называют «автономное тестирование и отладка». На втором этапе, называемом «комплексное тестирование и отладка», осуществляется объединение модулей и проверка внешних спецификаций программы.
Тестирование
На этапе тестирования программист обнаруживает наличие сшибок в программе, в то время как на этапе отладки локализует ошибки и устраняет их. Отладка и тестирование программы или модуля осуществляются обычно за несколько шагов.
Каждый цикл отладки позволяет программисту находить при тестировании все более «тонкие» ошибки.
Первая группа тестов может быть очень простой, поскольку •большинство программ содержит в числе прочих легковыявля- ■емые ошибки. Примером простого теста может быть следующий: при вводе данных в программу выдает ли она какие-либо выходные данные по завершении работы? Если будут получены хоть какие-то результаты работы программы, то становится возможным проверить, правильные ли они.
Вторая группа тестов должна удостоверить, что программа работает правильно при задании входных данных, принимающих как типичные, так и предельные значения для задачи. В идеальном случае типичные значения входных данных и ожидаемое поведение программы определяются в спецификации программы с тем, чтобы программа была обязательно проверена в условиях основного режима ее функционирования. Важную роль в тестировании может сыграть и хороший комментарий к модулям, так как детальное описание функционирования модуля само по себе определяет перечень тестовых входных данных.
При проверке программы на предельные значения входных данных в первую очередь следует добиваться правильности ее работы при предельно допустимых значениях данных, а затем проверять реакцию программы на данные, выходящие за допустимую область значений. Программа должна если не обнаруживать такие данные, то по крайней мере иметь средства для завершения работы в этих условиях.
Отладка
При поиске ошибки в программе, как и при решении подобного рода проблем в других областях человеческой деятельности, необходимо в первую очередь руководствоваться здравым смыслом. Применив некоторую последовательность тестов, программист может определить, в каких областях программы может находиться ошибка и где ее быть не должно, последовательно сужая тем самым область поиска до тех пор, пока ошибка не будет обнаружена. Методика отладки программы аналогична методике ее проектирования. Она заключается в разбиении общей задачи на все меньшие конкретные подзадачи до тех пор, пока она не будет полностью решена. Основным вопросом, который возникает при этом, является следующий: «Какие средства могут быть использованы для разбиения задачи?»
Наиболее эффективные инструменты отладки создаются в процессе проектирования и кодирования программы. На этих этапах разработки необходимо обеспечить определенную степень защиты программы. Средства защиты программы должны
проверять, являются ли допустимыми значения всех входных данных, а также быть готовы к появлению кажущихся невозможными ситуаций и сигнализировать о них. Поэтому последствия ошибок, как правило, быстро распознаются этими средствами, что значительно облегчает выявление и локализацию ошибок.
Кроме того, программист может разработать средства, обеспечивающие трассировку программы, распечатку состояния программы в контрольных точках, запуск и останов программы в любой ее точке. На первый взгляд может показаться, что написание программ значительного объема, которые впоследствии неизбежно будут выброшены, с точки зрения трудозатрат неэффективно. Однако следует иметь в виду, что эти программы дают существенную экономию времени отладки. У хорошего программиста средства отладки могут составлять до 50% всего объема разрабатываемой программы.
Во многих системах программирования на языках высокого уровня и в большинстве систем программирования на языках ассемблера перечисленные выше возможности могут быть использованы без написания дополнительных программ. В качестве примера можно назвать диалоговый отладчик, который представляет собой работающую в интерактивном режиме системную программу, предназначенную для отладки программ пользователей на языке ассемблера. Она обеспечивает следующие возможности:
Пошаговое выполнение. Программа пользователя реализуется команда за командой с возвратом управления отладчику после каждого шага.
Прогон. Выполнение программы пользователя начинается с произвольного адреса и осуществляется без прерываний.
Прогон с используемыми контрольными точками. При работе программы пользователя управление передается отладчику при выполнении команд, адреса которых указаны пользователем в списке контрольных точек.
Выведение на устройства отображения и изменение содержимого памяти. Пользователь имеет возможность вывести на устройства отображения или изменить данные, хранимые в памяти.
Выведение на устройства отображения и изменение содержимого регистров. Пользователь располагает возможностью вывести на устройства отображения или изменить последние значения, которые имели регистры процессора при выполнении программы.
Адресация с помощью символов. В некоторых отладчиках, называемых символьными отладчиками, имеется таблица символов ассемблера, позволяющая пользователю указывать адрес в оперативной памяти с помощью символа, а не числа.
Отладчики обычно имеются только в составе систем разработки программ. В программное обеспечение небольших специализированных микропроцессорных систем, предназначенных для работы в кассовом аппарате или автомобиле, отладчики не включаются, да они там и не нужны. Отладчик требуется только при разработке прикладных программ, но не при их эксплуатации.
Специализированные системы обычно создаются с использованием встроенного эмулятора. Встроенный эмулятор исключает необходимость подсоединять микропроцессор специализированной системы к ЭВМ вычислительной системы разработки. Система разработки обладает большей памятью, значительным количеством внешних устройств и более мощным программным обеспечением, что делает возможной совместную работу прикладных программ и программ отладчика.
Используя отладчик и необходимые листинги программы, можно очень быстро отладить программу, даже если она не располагает встроенными средствами отладки. Последовательность работ одного цикла отладки может быть следующей:
Прогнать программу с набором тестовых входных данных и выявить наличие ошибок.
Выделить область программы, в которой может находиться ошибка (например, главная программа, подпрограмма умножения, подпрограмма вывода и т. д.). Просмотреть листинг программы — иногда можно обнаружить ошибку в тексте программы. В противном случае установить контрольную точку непосредственно перед входом программы в выделенную область. Если эта область большая, установить контрольную точку примерно в ее середине.
Прогнать программу еще раз. Если работа программы была прервана до обработки контрольной точки, значит, ошибка произошла раньше; тогда перенести контрольную точку и возвратиться к шагу 2.
Если контрольная точка программы была обработана, изучить значения регистров, стеков, переменных и параметров подпрограммы для того, чтобы убедиться, что они правильны. Если ошибка имела место, вновь перенести контрольную точку и вернуться к шагу 2.
Если ошибка не обнаружена, продолжить выполнение программы покомандно, контролируя правильность выполнения переходов, а также содержимое регистров и памяти в контрольных точках. Если ошибка найдена, работа завершена. В противном случае возврат к шагу 1 или 2.
Если ошибка выявлена, ее можно исправить. Керниген и Плоджер установили мудрое правило: «Не ограничивайтесь нахождением одной ошибки». Логично предположить, что поблизости от найденной ошибки скрывается еще несколько, поэтому
следует тщательно проверить эту область программы. Кроме того, необходимо быть крайне внимательным, чтобы при исправлении старых ошибок в программе не внести новых.
Иногда время одного цикла отладки бывает довольно большим из-за необходимости каждый раз осуществлять редактирование, трансляцию и перезагрузку программы, поэтому следует стремиться выявить как можно больше ошибок за один цикл. Если найденные ошибки незначительны, то часто бывает возможно протестировать другую область программы либо исправить на скорую руку («залатать») одну или две команды с тем, чтобы продолжить отладку. Если для исправления ошибки необходимо исключить какие-то команды, то можно на их место просто вписать операторы NOP (Нет операции). Если же необходимо ввести дополнительные команды, то их можно записать в виде процедуры в свободной области памяти. Тогда «заплата» должна содержать оператор вызова процедуры, добавляемые команды в виде процедуры и команды, обеспечивающие возврат управления в требуемое место программы. При такой модификации программы необходимо быть особенно внимательным к длине записываемых команд (например, нельзя вписать 3-байтовый оператор JSR PATCH на место 1-байтового CLRA).
Практика разработки программного обеспечения показала, что можно выделить довольно большую группу ошибок, которые допускают программисты, начинающие работу на ассемблере:
Неправильная установка указателя стека в начале программы. '
Размещение данных вместе с командами. Необходимо записывать данные отдельно от выполняемого кода программы (см. разд. 6.1.3).
Изменение констант. Нельзя изменять данные, объявленные с помощью псевдокоманд FCB или FCW.
Неправильное использование констант, необходимых для ассемблирования или выполнения программы. В нижеследующих примерах показаны возможные пути записи в А символа J в коде ASCII:
LETJ
ZLETJ
ORG EQU
FCB
1234H
4AH
4AH
Определение идентичности символов LETJ и 4AH
Запись 4АН в память по адресу 1234Н
LDA
LETJ
Неверно, запись MEM [4АН] в А
LDA
#ZLETJ
Неверно, запись в А младшего байта слова 1234Н
LDA
#LETJ
Правильно
LDA
ZLETJ
Правильно
Неправильное использование указателя стека. Если управление программой передается в неизвестные области, скорее всего это является результатом выполнения команды RTS в конце процедуры, которая пытается записать в стек больше данных, чем считывает из него, или наоборот. Осторожнее употребляйте конструкции типа
SUBR
PSHS
А
Сохранение содержимого регистра А
BEQ
DONE
Выход в особом случае
Иначе продолжение процедуры
PULS
А
Восстановление регистра А
DONE
RTS
Возврат управления в вызывающую программу
Возврат управления из подпрограммы должен осуществляться посредством команды RTS, а не передачей управления непосредственно по какому-либо адресу главной программы.
Неверное использование псевдокоманды ORG. Обычно программист употребляет ее сначала для указания начального адреса размещения данных, впоследствии в более удобном месте программы с большим адресом он вновь использует эту команду для указания начального адреса размещения команд, не оставляя при этом достаточно места для только что объявленных данных. Это не вызовет ошибок при трансляции программы, однако они появятся при ее работе.
Неверное описание основания для констант. Пропуск Н или какого-либо другого префикса или суффикса для шестнадцатеричных или других констант является незначительной ошибкой, которая тем не менее может иметь самые различные последствия.
Неправильный формат выражений, записанных на языке ассемблера. Операторы языка могут приводить иногда к совершенно неожиданным результатам, которые не регистрируются в качестве ошибки. Например, выражение EBASE EQU BASE — —2*FIRST может быть обработано как (BASE—2)*FIRST или как BASE—(2*FIRST) в зависимости от приоритета операций. Результатом вычисления выражения CON EQU 3 +5 может оказаться 3, если транслятор воспримет пробел после 3 как начало поля комментария. Обратите внимание на то, соответствуют ли значения в колонке «Содержимое» листинга программы тому, что было определено в колонке «Операнды».
В заключение приведем некоторые полезные для отладки правила:
При программировании предусматривайте средства защиты программы.
При нахождении одной ошибки не прекращайте дальнейшего поиска ошибок.
Регистрируйте все найденные ошибки.
Отмечайте все сделанные «заплатки».
12.6. СОПРОВОЖДЕНИЕ ПРОГРАММЫ
Начинающие программисты, в том числе студенты, редко сталкиваются с вопросом сопровождения разработанных ими программ. Учебные программы редко используются кем-либо еще, кроме их авторов. В лучшем случае их передают знакомым, которые будут изучать соответствующий курс в следующем семестре, в надежде, что это им поможет (сомнительное предположение, не так ли?). При промышленной разработке программ последние создаются для эксплуатации их пользователями, которые рассчитывают на то, что программы обладают определенными качествами. При прогонах любой сколько-нибудь сложной программы будут неизбежно выявляться скрытые до сих пор ошибки, а пользователи будут выдвигать все новые и новые требования по совершенствованию программы. Таким образом, текст программы и даже ее спецификация и проект могут претерпевать частые изменения.
В разд. 12.3 подчеркивается, что для разработки большой программы и ее документации необходима большая вычислительная система с развитым программным обеспечением. Для проведения работ по сопровождению программ требуется вычислительная система, располагающая еще большими ресурсами и более развитым программным обеспечением. Потребуются административные программы, обеспечивающие рассылку возможным покупателям проспектов о новых программах или по крайней мере сообщений о возможности приобретения последней версии уже имеющейся у них программы. Потребуются средства слежения за всеми сделанными в программе исправлениями и усовершенствованиями; необходимость в этих средствах определяется еще и тем, что через некоторое время разные покупатели будут пользоваться различными версиями программ. Более глубокое изучение методов сопровождения программного обеспечения выходит за рамки данной книги. Источники, посвященные этому вопросу, указаны в разд. «Литература».
УПРАЖНЕНИЯ
Разработайте проект программы, описанной в упр. 10.14. Проект должен включать в себя блок-схему программы, структуру текста программы для всех модулей, за исключением модуля обработки очередей, и определения всех наборов данных.
Напишите комментарий к подпрограмме DIVIDE (рис. 9.15).
Напишите комментарий к подпрограммам опроса (рис. 11.6).
Перепишите программу (рис. 7.7) так, чтобы все безусловные переходы,, находящиеся в теле циклов, были вынесены за его пределы.
Перекодируйте цикл инициализации списка (разд. 12.4.2) так, чтобы
команда СМРХ NULL не выполнялась при NULL=0, но цикл работал правильно при NULLO0. (Указание. Используйте стандартный ассемблер.)
Напишите, какой требуется дополнительный комментарий, если программист меняет десятую строку подпрограммы QENQ (рис. 9.21) на следующую:
* CLRZ Установить Z = 0, поскольку очередь не заполнена
Операция сравнения обычно имеет три возможных результата: меньше чем, равно или больше чем. Очень часто только два из этих исходов представляют интерес, так как третий исход бывает невозможен. Например, в цикле, который уменьшает значение счетчика от некоторой положительной величины и заканчивается, когда счетчик устанавливается в 0, счетчик не должен принимать отрицательного значения. Тем не менее программист, создающий защищенную программу, предпочитает использовать команду BLE DONE вместо BEQ DONE для проверки завершения цикла с целью ограничить последствия ошибки, которая каким-либо образом может придать счетчику отрицательное значение (в противном случае программа может зациклиться). Найдите в этой книге по крайней мере четыре программы на Паскале или ассемблере, в которых этот принцип соблюдается. Укажите также не менее четырех программ, в которых этот принцип нарушается, и покажите, как их надо исправить. ~ - -
ЛИТЕРАТУРА
Практика проектирования, разработки документации, кодирования и отладки программ довольно полно изложена в книге Брайена, Кернигена и Пло- джера (Brian W., Kernighan, Plauger Р. J., The Elements of Programming Style, 2nd ed., McGraw-Hill, 1978). Лица, программирующие на Паскале, могут почерпнуть много полезного из работы, посвященной этому вопросу (Schneider, Weingart, Perlman, An Introduction to Programming and Problem Solving with Pascal, Wiley, 1978).
Большое количество хороших книг выпущено по различным специальным вопросам разработки программного обеспечения. Классической среди них является книга Брукса (Brooks F. Р., The Mythical Man-Month, Addison-Wesley, 1975. [Имеется перевод: Брукс Ф. П. Как проектируются и создаются программные комплексы. — М.: Наука, 1979]). Эта короткая содержательная и легкочитаемая книга представляет собой побуждающее к размышлению эссе о сущности и методах управления разработкой программного обеспечения и должна быть изучена всеми, кто принимает участие в создании больших программ. Ее автор дает конкретные рекомендации по разработке спецификаций, проекта, документации, кодированию, тестированию и отладке, а также обсуждает общие проблемы управления разработкой больших программ.
Полезной является также книга трех авторов (Zelkowitz, Shaw, Gannon, Principles, of-Software Engineering and Design, Prentice-Hall, 1979. [Имеется перевод: Зелковиц M., Шоу А., Гэннон Дж. Принципы разработки программного обеспечения. — М.: Мир, 1982]). Главная идея этой книги иллюстрируется диаграммой, приведенной на рис. 12.2. Интересным как для программистов, так и для руководителей является труд Уэйнберга (Weinberg М., The Psychology of Computer Programming, Van Nostrand Reinhold, 1971). В нем исследуются вопросы поведения и мышления программистов в процессе выполнения ими своих повседневных обязанностей и то, как их особенности отражаются на различных этапах разработки программ.
ОГЛАВЛЕНИЕ
Предисловие редактора перевода• ••••♦5
Предисловие ........7
Предисловие для преподавателей12
Глава 1. Введение в вычислительную технику и программирование 17
Что такое ЭВМ?17
Аппаратные средства цифровых вычислительных машин18
Базовая организация ЭВМ23
Программное обеспечение вычислительных систем ...27
Алгоритмы30
Типы данных ЭВМ32
Классификация вычислительных машин34
Литература36
Глава2. Язык программированияПаскаль38
Общие представления39
Лексика43
Структура программы46
Описания49
Стандартные типы данных и выражения52
Типы данных, определяемые пользователем55
Простые операторы59
Структурные операторы (группа I)63
Структурные операторы (группа II)70
Упражнения76
Литература78
Глава3. Структурыданныхвпрограммах на языке Паскаль ...79
Одномерные массивы80
Многомерные массивы84
Стеки87
Очереди93
Однонаправленные связанные списки98
Двунаправленные связанные списки103
Управление памятью при работе со списками ....105
Упражнения107
Литература111
Глава 4. Системы счисления и арифметические операции ..112
Позиционные системысчисления112
Восьмеричныеишестнадцатеричные числа114
Основные способы преобразования чисел в позиционных системах счисления115
Сложение и вычитаниенедесятичных чисел115
Представление отрицательных чисел125
Сложение и вычитаниечисел в дополнительном коде..125
Сложение и вычитаниечисел в обратном коде....131
Двмчное умножение133*
Двоичное деление138*
0. Двоично-десятичная система представления чисел...145
Представление чисел в форматах с фиксированной и плавающей точками144
Кодирование символов152
Упражнения153
Литература ...............156
Глава 5. Базовая структура (огранизация) ЭВМ157
Память158
Процессоры с аккумуляторами162
Процессоры с регистрами общего назначения ....184
Машины со стековой организацией192
Другие типы организации процессоров и их классификация204
Упражнения207
Литература ..211
Глава6. Программирование на языке ассемблера212
Язык ассемблера215
Двухпроходный ассемблер224
Объектные модули и загрузчики229
Перемещающие ассемблеры и загрузчики232
Макрокоманды237
Структурный язык ассемблера242
Упражнения . . .. 245
Литература245
Глава7. Способы адресации 249*
Общие понятия250
Однокомпонентные способы адресации252
Многокомпонентные способы адресации 262'
Позиционно-независимая программа277
Блок преобразования адресов памяти280
Упражнения288
Литература289
Глава8. Операции295
Форматы команд291
Разряды условий 295
Пересылка данных 295
Сложение и вычитание . '298
Команды с одним операндом.301
Управление выполнением программы303
Логические операции‘309
Циклические, логические и арифметические сдвиги . ..311
Сложение и вычитание с повышенной точностью...317
Умножение и деление319
Десятичная арифметика ....321
Упражнения . . . . 325
Литература ...329
Глава 9. Подпрограммыи параметры 330
Процедуры ифункции языкаПаскаль331
Параметры в программах, представленных на языках высокого уровня337
Подпрограммы и параметрыязыкаассемблера ....343
Рекурсия356
Сопрограммы361
Упражнения 368
Литература ..*.....371
Глава10. Организацияввода-вывода373
Подсистема ввода-вывода373
Программирование ввода-вывода378
10:3. Протоколы ‘ввода-вывода \.‘. \384
Драйверы ввода-вывода390
Упражнения397
Литература 399
Глава 11. Организация прерываний и прямого доступа к памяти ..400
400409416418423435448451400409416418423435448451Основная структура системы прерываний и программирование
400
409
416
418
423
435
448
451
400
409
416
418
423
435
448
451
Типы систем прерываний ..
Специальные и* программные* прерывания
Прямой доступ к памяти
Процессы прерываний
Использование данных и команд несколькими процессами Упражнения Литература
Глава12. Разработкапрограмм453
Этапы жизненного циклапрограмм454
Составление спецификацийипроектирование ....456
Документация462
Кодирование 469
Тестирование и отладка475
Сопровождение программы482
Упражнения482
Литература483
Джон Уокерли
АРХИТЕКТУРА И ПРОГРАММИРОВАНИЕ МИКРО-ЭВМ,
Кн. 1
Научный редактор Л. А. Паршина
Младший научный редактор Ю. Л. Евдокимова
Художник А. В. Смеляков
Художественный редактор В. Б. Прищепа
Технический редактор Т. А. Максимова
Корректор Т. П. Пашковская
ИБ № 3969
Сдано в набор 27.06.83. Подписано к печати 28.10.83. Формат 60X90V16.
Бумага типографская № 2. Гарнитура литературная. Печать высокая. Объем 15,25 бум. л. Усл. печ. л. 30,5. Усл. кр.-отт. 30,5. Уч.-изд. л. 30,48. Изд. № 20/2315. Тираж 25000 экз. Зак. 1393. Цена 2 р. 50 к.
ИЗДАТЕЛЬСТВО «МИР», 129820,
Москва, И-110, ГСП, 1-й Рижский пер., 2.
Московская типография № 11 Союзполиграфпрома при Государственном комитете СССР по делам издательств, полиграфии и книжной торговли.. Москва, 113105, Нагатинская ул., д. 1.
УВАЖАЕМЫЙ ЧИТАТЕЛЬ!
Ваши замечания о содержании книги, ее оформлении, качестве перевода и другие просим присылать по адресу: 129820, Москва, И-110, ГСП, 1-й Рижский пер., д. 2, изд-во «Мир».
В случае отсутствия точки в числе предполагается, что она находится справа от правой крайней цифры.
Любое число в позиционной системе счисления, если не учитывать возможных нулей в крайних позициях (разрядах),представляется единственным образом (очевидно, что число 34.85 равно чис&у 034.85000 и т. п.). Крайняя слева'цифра в таком числе называется цифрой старшего разряда, а крайняя справа — цифрой младшего разряда.
Почти во всех вычислительных машинах используется двоичная система счисления — система, основание которой равно двум. Цифры 0 и 1, используемые в такой системе, называют битами, каждый бит di имеет вес 2‘. Ниже приведены примеры двоичных чисел и их десятичных эквивалентов, в которых для указания системы счисления используется нижний индекс:
10012= Ь16+0-8+0.4+0.24-Ы = 1710
1010102= 1-32 + 0.16+Ь 8+0-4+1 -2+0.1=4210
110.0112=Ь 4+Ь 2+0-1+0-0.5+Ь 0.25+1-0.125 = 6.3751О
8—1393
11 См.: Уокерли Дж. Сумматор обратных кодов с исключением нежелательного нуля. — Электроника, 1976, т. 49, № 3(477).