
Boroda_2
.doc
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 для вызова сопрограмм не подходят, поскольку, хотя адрес перехода берется из стека, как и при возвращении управления, но в отличие от возвращения управления при вызове сопрограммы адрес возврата помещается в определенном месте, чтобы в последующем к нему вернуться. Было бы неплохо, если бы существовала команда, в которой вместо вершины стека использовался счетчик команд. Эта команда сначала должна выталкивать старый адрес возврата из стека и помещать его во внутренний регистр, затем помещать счетчик команд в стек и, наконец, копировать содержание внутреннего регистра в счетчик команд. Поскольку одно слово выталкивается из стека, а другое помещается в стек, состояние указателя стека не меняется. Такая команда встречается очень редко, поэтому в большинстве случаев ее приходится моделировать из нескольких команд.
Перехват исключений
Перехват исключений — это особый тип вызова процедур, который происходит при определенных условиях, обычно очень серьезных, но редко встречающихся. Один из примеров такого условия — переполнение. В большинстве процессоров, если результат выполнения арифметической операции превышает самое большое допустимое число, происходит исключение, которое перехватывается. Это значит, что поток управления переходит в какую-то фиксированную ячейку памяти, а не продолжается последовательно дальше. В этой фиксированной ячейке находится команда перехода к специальной процедуре (обработчику исключений), которая выполняет какое-либо определенное действие, например печатает сообщение об ошибке. Если результат операции находится в пределах допустимого, исключения не происходит.