Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Boroda_2

.doc
Скачиваний:
9
Добавлен:
20.03.2015
Размер:
1.67 Mб
Скачать

CPL

Образование дополнения EXT

Да

RL

Циклический сдвиг EXT влево

Да

RLC

Циклический сдвиг EXT влево путем переноса

Да

RR

Циклический сдвиг EXT вправо

Да

RRC

Циклический сдвиг EXT вправо через перенос

Да

CLR

Сброс бита

Да

Да

SETB

Установка бита

Да

Да

CPL

Дополнение бита

Да

Да

ANL

SCR И перенос

Да

ANL

Дополнение SCR И перенос

Да

ORL

SCR ИЛИ перенос

Да

ORL

Дополнение SCR ИЛИ перенос

Да

MOV

Перемещение SCR к переносу

Да

MOV

Перемещение переноса к SCR

Да

JV

Относительный переход (если перенос)

JNC

Относительный переход (если нет переноса)

JB

Относительный переход (если установлен бит прямого доступа)

Да

JNB

Относительный переход (если бит прямого доступа не установлен)

Да

JBC

Относительный переход (если бит прямого доступа установлен, а переноса нет)

Да

ACALL

Вызов подпрограммы (11-разрядная адресация)

LCALL

Вызов подпрограммы (16-разрядная адресация)

RET

Возврат после вызова подпрограммы

RETI

Возврат после прерывания

SJMP

Короткий относительный переход (8-разрядная адресация)

AJMP

Абсолютный переход (11-разрядная адресация)

UMP

Абсолютный переход (16-разрядная адресация)

JMP

Косвенный переход относительно DPR + ACC

JZ

Переход при нулевом значении АСС

JNZ

Переход при ненулевом значении АСС

CJNE

Сравнение SCR с АСС, переход при неравенстве

Да

Да

CJNE

Сравнение SCR с непосредственным операндом, переход при неравенстве

Да

Да

DJNZ

Отрицательное приращение EXT, переход при неравенстве нулю

NOP

Пустая операция

Эта операции устанавливает (делает равным 1) бит 43, но не оказывает влия­ния на остальные биты того же байта. За командами побитовых операций в таб­лице следуют команды передачи управления, в частности переходов, вызовов подпрограмм, условных переходов (две команды) и сравнения, а также команда DJNZ, предназначенная для организации циклов.

Сравнение наборов команд

Рассмотренные наборы команд разительно отличаются друг от друга. Pentium 4 — это классическая двухадресная 32-разрядная CISC-машина. Она пе­режила долгую историю, у нее особые и нерегулярные режимы адресации, и многие ее команды обращаются непосредственно к памяти. UltraSPARC III — это современная трехадресная 64-разрядная RISC-машина с архитектурой за­грузки/сохранения, всего двумя режимами адресации, компактным и эффектив­ным набором команд. Архитектура 8051 рассчитана на небольшой встроенный процессор, устанавливаемый на единственную микросхему.

В основе набора команд компьютера Pentium 4 лежат три основополагающих фактора:

+ обратная совместимость;

+ обратная совместимость;

+ и еще раз обратная совместимость.

При нынешнем положении вещей никто не стал бы разрабатывать такую не­регулярную машину с таким небольшим количеством абсолютно разных регист­ров. По этой причине для Pentium 4 очень сложно писать компиляторы. Из-за недостатка регистров компиляторам постоянно приходится сохранять перемен­ные в памяти, а затем вновь загружать их, что очень невыгодно даже при 3-уров- невой кэш-памяти. Только благодаря таланту инженеров компании Intel процес­сор Pentium 4 работает достаточно быстро, несмотря на все недостатки его архитектуры команд. Но, как мы видели в главе 4, конструкция этого процессора чрезвычайно сложна.

Весьма современный уровень архитектуры набора команд представлен в про­цессоре UltraSPARC III. Это — полная 64-разрядная архитектура (с шиной 128 бит). Процессор содержит множество регистров, а в наборе команд преобла­дают 3-регистровые операции; имеется также небольшая группа команд загрузки и сохранения. Все команды одного размера, хотя число форматов совершенно невообразимо. Большинство новых разработок очень похоже на UltraSPARC III, но форматов команд у них меньше.

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

Поток управления

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

Последовательный поток управления и переходы

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

Если программист не знает, в какой последовательности процессор будет вы­полнять команды, это может привести к ошибкам. Такое наблюдение вызвало к жизни известную статью о необходимости избегать в программах оператора goto [55]. Эта статья привела к революции в программировании, одним из результа­тов которой стало появление операторов, позволяющих лучше структурировать поток управления, чем оператор goto. Естественно, подобные программы в конце концов все равно компилируются в программы уровня 2, содержащие многочис­ленные переходы, которых требует реализация операторов if, while и тому по­добных высокоуровневых структур.

Процедуры

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

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

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

Ханойская башня — это название древней задачи, которая благодаря рекур­сии имеет простое решение. В одном монастыре в Ханое есть три золотых ко­лышка. На первый из них надето 64 концентрических золотых диска. Диаметр дисков уменьшается снизу вверх. Второй и третий колышки пусты. Монахи должны по одному перенести все диски на третий колышек, используя при необ­ходимости второй колышек, но при этом после каждой итерации ни на одном из колышков диск большего диаметра не может оказаться на диске меньшего диа­метра. Говорят, что когда они закончат, наступит конец света. Если вы хотите потренироваться, можете использовать пластиковые диски, и не 64, а поменьше. (Будем надеяться, что когда вы решите эту задачу, ничего страшного не про­изойдет. Чтобы произошел конец света, требуются именно 64 диска, причем обя­зательно золотых.) На рис. 5.23 показана начальная конфигурация для случая, когда число дисков (п) равно 5.

Чтобы переместить п дисков с колышка 1 на колышек 3, нужно сначала пере­нести 72-1 дисков с колышка 1 на колышек 2, затем перенести один диск с ко­лышка 1 на колышек 3, потом перенести п - 1 диск с колышка 2 на колышек 3. Решение этой задачи для трех дисков иллюстрирует рис. 5.24.

Для решения задачи нам нужна процедура, которая позволяет переместить п дисков с колышка i на колышек j:

towers (n, i, j)

После вызова этой процедуры решение должно выводиться на экран. Сначала процедура проверяет, равно ли единице значение п. Если да, то решение триви­ально: нужно просто переместить один диск с i на j. Если п не равно 1, решение состоит из трех частей и каждая из этих частей представляет собой рекурсивную процедуру.

Все решение представлено в листинге 5.6. Рассмотрим такой вызов процедуры:

towers (3, 1, 3)

Этот вызов порождает еще три вызова:

towers (2, 1, 2) towers (1, 1, 3) towers (2, 2, 3)

Первый и третий вызовы производят по три вызова каждый, и всего полу­чится семь.

Листинг 5.6. Процедура для решения задачи «Ханойская башня»

public void towers (int n, int i, int j) { int k; if (n == 1)

System.out.printlnCTlepeMecTMTb диск с " + i + "на" + j); else { k=6-i-j;

towers(n-l, i, k): towers (1, i, j); towers (n-1, k. j);

}

}

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

Помимо указателя стека (Stack Pointer, SP), который указывает на вершину стека, удобно иметь указатель фрейма (Frame Pointer, FP), указывающий на за­данное место во фрейме, например, на связующий указатель, как в IJVM, или на первую локальную переменную.

На рис. 5.25 изображен стековый фрейм для машины с 32-разрядным словом. При первом вызове процедуры towers в стек помещаются значения n, i и j, а за­тем выполняется команда CALL, которая помещает в стек адрес возврата, 1012. Вызванная процедура сохраняет в стеке старое значение FP (1000) в ячейке 1016, а затем передвигает указатель стека для обозначения места хранения ло­кальных переменных.

При наличии только одной 32-разрядной локальной переменной (к), указа­тель стека увеличивается на 4 — до 1020. На рис. 5.25, а показан результат всех этих действий.

Первое, что должна сделать процедура после вызова, — сохранить предыдущее значение FP (так, чтобы его можно было восстановить при выходе из процедуры), скопировать значение SP в FP и, возможно, увеличить SP на одно слово, в зависи­мости от того, куда указывает FP нового фрейма. В этом примере FP указывает на первую локальную переменную (хотя в IJVM регистр LV указывал на связующий указатель). Разные машины оперируют указателем фрейма немного по-разному, иногда помещая его в самый низ стекового фрейма, иногда — в вершину, а иногда — в середину, как на рис. 5.25. В этом отношении стоит сравнить рис. 5.25 с рис. 4.10, чтобы познакомиться с двумя разными способами обращения со связующим указа­телем. Возможны и другие способы. Но в любом случае обязательно должна быть возможность выйти из процедуры и восстановить предыдущее состояние стека.

Код, который сохраняет старый указатель фрейма, устанавливает новый указатель фрейма и увеличивает указатель стека, чтобы зарезервировать пространство для ло­кальных переменных, называется прологом процедуры. При выходе из процедуры стек должен быть очищен, и эта задача решается в эпилоге процедуры. Одна из важ­нейших характеристик компьютера — насколько быстро он может выполнять пролог и эпилог. Если они очень длинные и выполняются медленно, вызывать процедуры оказывается невыгодно. Команды ENTER и LEAVE в Pentium 4 были разработаны специ­ально для того, чтобы эффективно работали прологи и эпилоги процедур. Конечно, они поддерживают определенную модель обращения с указателем фрейма, и, если компилятор поддерживает другую модель, использовать эти команды нельзя.

А теперь вернемся к Ханойской башне. Каждый вызов процедуры добавляет новый фрейм в стек, а каждый выход из процедуры удаляет фрейм из стека. По­смотрим, как используется стек при реализации рекурсивных процедур, и нач­нем с вызова

towers (3, 1, 3)

На рис. 5.25, а показано состояние стека сразу после вызова процедуры. Сна­чала процедура проверяет, равно ли п единице, а установив, что п = 3, заполняет к и совершает вызов

towers (2, 1, 2)

Состояние стека после завершения этого вызова показано на рис. 5.25, 6. Да­лее процедура выполняется снова (вызванная процедура всегда начинается с на­чала). На этот раз условие n = 1 снова не подтверждается, поэтому процедура снова заполняет к и совершает вызов

towers (1, 1, 3)

Состояние стека после этого вызова показано на рис. 5.25, в. Счетчик команд указывает на начало процедуры. На этот раз условие подтверждается, и на экран выводится строка. Затем совершается выход из процедуры. Для этого удаляется один фрейм, а значения FP и SP переопределяются (рис. 5.25, г). Далее продол­жается выполнение процедуры с адреса возврата:

towers (1, 1, 2)

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

towers (3, 1. 3)

Сопрограммы

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

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

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

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

Однако иногда нужно, чтобы обе процедуры (А и В) вызывали друг друга в качестве процедуры, как показано на рис. 5.27. При возврате из В в А процеду­ра В совершает переход к тому оператору, перед которым последовал вызов про­цедуры В. Когда процедура А передает управление процедуре В, она возвращает­ся не к самому началу В (за исключением первого раза), а к тому месту, перед которым произошел предыдущий вызов А. Две процедуры, работающие подоб­ным образом, называются сопрограммами.

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

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

Рис. 5.27. После завершения сопрограммы выполнение начинается с того места, на котором оно завершилось в прошлый раз, а не с самого начала

Перехват исключений

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]