Типы команд
Команды можно грубо поделить на несколько групп, которые повторяются от машины к
машине, хотя и могут различаться в деталях. Кроме того, в каждом компьютере всегда имеется
несколько необычных команд, которые добавлены в целях совместимости с предыдущими
моделями.
Команды перемещения данных
Копирование данных из одного места в другое — одна из самых распространенныхопераций.
Под копированием мы понимаем создание нового объекта с точно таким же набором битов, как у
исходного объекта. Такое понимание слова «перемещение» несколько отличается от его обычного
значения. Когда мы говорим, что содержимое ячейки памяти 2000 переместилось в какой-либо
регистр, мы всегда подразумеваем, что там была создана идентичная копия и что оригинал все еще
находится в ячейке 2000.
Есть две причины, по которым данные могут копироваться из одного места в другое. Одна из
них фундаментальна: присваивание переменным значений. Операция присваивания a = b;
выполняется путем копирования значения, которое находится в ячейке памяти с адресом В, в ячейку
А. Вторая причина копирования данных — предоставить возможность быстрого обращения к ним.
Поскольку существует два возможных источника элемента данных (память и регистр) и существует
два возможных пункта назначения для элемента данных (память и регистр), следовательно,
существует 4 различных способа копирования. В одних компьютерах содержится 4 команды для 4
случаев, в других — одна команда для всех 4 случаев. Команды перемещения данных должны как-то
указывать, какое именно количество данных нужно переместить. Существуют команды для
перемещения разного количества данных — от одного бита до всей памяти. В машинах с
фиксированной длиной слова обычно перемещается ровно одно слово. Любые перемещения
другого количества данных (больше слова или меньше слова) должны выполняться программным
обеспечением с использованием сдвигов и слияний.
Бинарные операции
Бинарные операции — это такие операции, которые берут два операнда и получают из них
результат. Все архитектуры команд содержат команды для сложения и вычитания целых чисел.
Следующая группа бинарных операций содержит булевы команды. Существует 16 булевых
функций от двух переменных, но есть очень немного машин, в которых имеются команды для всех
16. Обычно присутствуют И, ИЛИ и НЕ; иногда кроме них еще есть ИСКЛЮЧАЮЩЕЕ ИЛИ, НЕ-ИЛИ и
НЕ-И.
Важным применением команды И является выделение битов из слов. Рассмотрим машину со
словами длиной 32 бита, в которой на одно слово приходится четыре 8-битных символа.
Предположим, что нужно отделить второй символ от остальных трех, чтобы его напечатать. Это
значит, что нужно создать слово, которое содержит этот символ в правых 8 битах с нулями в левых 24
битах (так называемое выравнивание по правому биту).
Чтобы извлечь нужный нам символ, слово, содержащее этот символ, соединяется операцией И
с константой, которая называется маской. В результате этой операции все ненужные биты меняются
на нули:
10110111 10111100 11011011 10001011 А
00000000 11111111 00000000 00000000 В (маска)
00000000 10111100 00000000 00000000 А И В
Затем результат сдвигается на 16 битов вправо, чтобы нужный символ находился в правом
конце слова.Важным применением команды ИЛИ является помещение битов в слово. Эта операция
обратна операции извлечения. Чтобы изменить правые 8 битов 32-битного слова, не повредив при
этом остальные 24 бита, сначала нежелательные 8 битов надо заменить на нули, а затем новый
символ соединить операцией ИЛИ с полученным результатом, как показано ниже:
10110111 10111100 11011011 10001011 А
11111111 11111111 11111111 00000000 В (маска)
10110111 10111100 11011011 00000000 А И В
00000000 00000000 00000000 01010111 С
10110111 10111100 11011011 01010111 (АИВ) ИЛИ С
Операция И убирает единицы, и в полученном результате никогда не бываетбольше единиц,
чем в любом из двух операндов. Операция ИЛИ вставляет единицы, и поэтому в полученном
результате всегда по крайней мере столько же единиц, сколько в операнде с большим количеством
единиц. Команда ИСКЛЮЧАЮЩЕЕ ИЛИ, в отличие от них, симметрична в отношении единиц и нулей.
Такаясимметрия иногда может быть полезной, например при порождении случайных чисел.
Унарные операции
Унарные операции используют один операнд и производят один результат. Поскольку в
данном случае нужно определять на один адрес меньше, чем в бинарных операциях, команды
иногда бывают короче, хотя часто требуется определять другую информацию.
Команды для сдвига и циклического сдвига очень полезны. Они часто даются в нескольких
вариантах. Сдвиги — это операции, при которых биты сдвигаются налево или направо, при этом
биты, которые сдвигаются за пределы слова, утрачиваются. Циклические сдвиги — это сдвиги, при
которых биты, вытесненные с одного конца, появляются на другом конце. Разница между обычным
сдвигом и циклическим сдвигом показана ниже:
00000000 00000000 00000000 01110011 А
00000000 00000000 00000000 00011100 сдвиг вправо на 2 бита
11000000 00000000 00000000 00011100 циклический сдвиг вправо на 2 бита
Обычные и циклические сдвиги влево и вправо очень важны. Если n-битноеслово циклически
сдвигается влево на к битов, результат будет такой же, как при циклическом сдвиге вправо на n-k
битов.
Сдвиги вправо часто выполняются с расширением по знаку. Это значит, что позиции,
освободившиеся на левом конце слова, заполняются изначальным знаковым битом (0 или 1), как
будто знаковый бит перетащили направо. Кроме того, это значит, что отрицательное число останется
отрицательным. Ниже показаны сдвиги на 2 бита вправо:
1111111 11111111 11111111 11110000А
0011111 11111111 11111111 11111100 А сдвинуто без знакового расширения
1111111 11111111 11111111 11111100 А сдвинуто со знаковым расширением
Операция сдвига используется при умножении и делении на 2.
В некоторых бинарных операциях очень часто используются совершенно определенные
операнды, поэтому в архитектуры команд часто включаются унарные операции для их быстрого
выполнения. Например, перемещение нуля в память или регистр чрезвычайно часто выполняется
при начале вычислений. Перемещение нуля — это особый случай команды перемещения данных.
Поэтому для повышения производительности часто вводится операция CL R с единственным адресом
той ячейки, которую нужно очистить (то есть установить на 0).
Сравнения и условные переходы
Практически все программы должны проверять свои данные и на основе результатов изменять
последовательность команд, которые нужно выполнить.
Это можно сделать с помощью специальных команд условного перехода, которые проверяют
какое-либо условие и совершают переход в определенный адрес памяти, если условие выполнено.
Иногда определенный бит в команде указывает, нужно ли осуществлять переход в случае
выполнения условия или в случае невыполнения условия соответственно. Часто целевой адрес
является не абсолютным, а относительным (он связан с текущей командой).
Самое распространенное условие, которое нужно проверить, — равен ли определенный бит
нулю или нет. Если команда проверяет знаковый бит числа и совершает переход к метке (LABEL) при
условии, что бит равен 1, то если число было отрицательным, будут выполняться те утверждения,
которые начинаются с метки LABEL, а если число было положительным или было равно 0, то будут
выполняться те утверждения, которые следуют за условным переходом.
Во многих машинах содержатся биты кода условия, которые указывают на особые условия.
Например, там может быть бит переполнения, который принимает значение 1 всякий раз, когда
арифметическая операция выдает неправильный результат. Проверяя этот бит, мы проверяем
выполнение предыдущей арифметической операции, и если произошла ошибка, то запускается
программа обработки ошибок.
Проверка на ноль очень важна при выполнении циклов и в некоторых других случаях. Если бы
все команды условного перехода проверяли только 1 бит, то тогда для проверки определенного
слова на 0 нужно было бы отдельно проверять каждый бит, чтобы убедиться, что ни один бит не
равен 1. Чтобы избежать подобной ситуации, во многие машины включается команда, которая
должна проверять слово и осуществлять переход, если оно равно 0.
Операция сравнения слов или символов очень важна, например, при сортировке. Чтобы
произвести сравнение, требуется три адреса: два нужны для элементов данных, а в третий адрес
будет совершаться переход в случае выполнения условия. В тех компьютерах, где форматы команд
позволяют содержать три адреса в команде, проблем не возникает. Но если такие форматы не
предусмотрены, нужно что-то сделать, чтобы обойти эту проблему. Одно из возможных решений —
ввести команду, которая выполняет сравнение и записывает результат в один или несколько битов
условия. Такой подход применяется в Pentium II и UltraSPARC II.
В сравнении двух чисел есть некоторые тонкости. Сравнение — это не такая простая операция,
как вычитание. Если очень большое положительно число сравнивается с очень большим
отрицательным числом, операция вычитания приведет к переполнению, поскольку результат
вычитания не может быть представлен. При сравнении не должно быть переполнений.
Команды вызова процедур
Процедура — это группа команд, которая выполняет определенную задачу и которую можно
вызвать из нескольких мест программы. Вместо термина процедура часто используется термин
подпрограмма, особенно когда речь идет о программах на языке ассемблера. Когда процедура
закончила задачу, она должна вернуться к соответствующему оператору.
Адрес возврата может помещаться в одном из трех мест: в памяти, в регистре или в стеке.
Самое худшее решение — поместить этот адрес в одну фиксированную ячейку памяти. Тогда если
процедура будет вызывать другую процедуру, второй вызов приведет к потере первого адреса
возврата.
Более удачное решение — сохранить адрес возврата в первом слове процедуры. Тогда первой
выполняемой командой будет второе слово процедуры. После завершения процедуры происходит
переход к первому слову, а если аппаратное обеспечение в первом слове наряду с адресом возврата
дает код операции, то происходит непосредственный переход к этой операции. Процедура может
вызывать другие процедуры, поскольку в каждой процедуре имеется пространство для одного
адреса возврата. Но если процедура вызывает сама себя, эта схема не работает, поскольку первый
адрес возврата будет уничтожен вторым вызовом. Способность процедуры вызывать саму себя,
называемая рекурсией.
Еще более удачное решение — помещать адрес возврата в регистр. Если процедура
рекурсивна, ей придется помещать адрес возврата в другое место каждый раз, когда она вызывается.
Самое лучшее решение — поместить адрес возврата в стек. Когда процедура завершена, она
выталкивает адрес возврата из стека.
Управление циклом
Часто возникает необходимость выполнять некоторую группу команд фиксированное
количество раз, поэтому некоторые машины содержат команды для облегчения этого процесса. Все
эти схемы содержат счетчик, который увеличивается или уменьшается на какую-либо константу
каждый раз при выполнении цикла. Кроме того, этот счетчик каждый раз проверяется. При
выполнении определенного условия цикл завершается.
Определенная процедура запускает счетчик вне цикла и затем сразу начинает выполнение
цикла. Последняя (или первая) команда цикла обновляет счетчик, и если условие завершения цикла
еще не выполнено, то происходит возврат к первой команде цикла. Если условие выполнено, цикл
завершается и начинается выполнение команды, идущей сразу после цикла.
Команды ввода-вывода
Ни одна другая группа команд не различается настолько сильно в разных машинах, как
команды ввода-вывода. В современных персональных компьютерах используются три различные
схемы ввода-вывода:
1. Программируемый ввод-вывод с активным ожиданием.
2. Ввод-вывод с управлением по прерываниям.
3. Ввод-вывод с прямым доступом к памяти.
Самым простым методом ввода-вывода является программируемый ввод-вывод, который
часто используется в дешевых микропроцессорах. Эти процессоры обычно имеют одну входную и
одну выходную команды. Каждая из этих команд выбирает одно из устройств ввода-вывода. Между
фиксированным регистром в процессоре и выбранным устройством ввода-вывода передается один
символ. Процессор должен выполнять определенную последовательность команд при каждом
считывании и записи символа. Основной недостаток программируемого ввода-вывода заключается в
том, что центральный процессор проводит большую часть времени в цикле, ожидая готовности
устройства. Такой процесс называется активным ожиданием. Если центральному процессору
больше ничего не нужно делать в этом нет ничего страшного. Но если процессору нужно выполнять
еще какие-либо действия, например запускать другие программы, то активное ожидание здесь не
подходит, и нужно искать другие методы ввода-вывода.
Чтобы избавиться от активного ожидания, нужно, чтобы центральный процессор запускал
устройство ввода-вывода и указывал этому устройству, что необходимо осуществить запрос на
прерывание, когда оно завершит свою работу. Во многих компьютерах сигнал прерывания
порождается путем логического умножения (И) бита разрешения прерываний и бита готовности
устройства. Если программное обеспечение сначала разрешает прерывание (перед запуском
устройства ввода-вывода), прерывание произойдет сразу же, поскольку бит готовности будет равен
1. Таким образом, может понадобиться сначала запустить устройство, а затем сразу после этого
ввести прерывание. Запись байта в регистр состояния устройства не изменяет бита готовности,
который может только считываться. Ввод-вывод с управлением по прерываниям — это большой шаг
вперед по срав- нению с программируемым вводом-выводом, но все же он далеко не совершенен.
Дело в том, что прерывание требуется для каждого передаваемого символа. Следовательно, нужно
каким-то образом избавиться от большинства прерываний.
Решение лежит в возвращении к программируемому вводу-выводу. Но только эту работу
должен выполнять кто-то другой.Добавляется новая микросхема — контроллер прямого доступа к
памяти (ПДП) с прямым доступом к шине (рис. 5.20.)
Рис. 5.20. Система с контроллером прямого доступа к памяти.
Микросхема ПДП имеет по крайней мере 4 регистра. Все они могут загружаться программным
обеспечением, работающим на центральном процессоре. Первый регистр содержит адрес памяти,
который нужно считать или записать. Второй регистр содержит число, которое показывает
количество передаваемых байтов или слов. Третий регистр содержит номер устройства или адрес
устройства ввода-вывода, определяя, таким образом, какое именно устройство требуется.
Четвертый регистр сообщает, должны ли данные считываться с устройства или записываться на него.
Чтобы записать блок из 32 байтов из адреса памяти 100 на терминал, центральный процессор
записывает числа 32,100 и 4 в первые три регистра ПДП и код записи (например, 1) в четвертый
регистр, как показано39 0 Глава 5. Уровень архитектуры команд на рис. 5.20. Контроллер ПДП,
инициализированный таким способом, делает запрос на доступ к шине, чтобы считать байт 100 из
памяти, точно так же как если бы центральный процессор считывал этот байт. Получив нужный байт,
контроллер ПДП посылает устройству 4 запрос на ввод-вывод, чтобы записать на него байт. После
завершения этих двух операций контроллер ПДП увеличивает значение регистра адреса на 1 и
уменьшает значение регистра счетчика на 1. Если значение счетчика больше 0, то следующий байт
считывается из памяти и записывается на устройство ввода-вывода.
Команды процессора Pentium II
Команды Pentium II представляют собой смесь команд 32-битного формата и команд, которые
восходят к процессору 8088.
Рис. 5.21. Команды работы с целыми числами
Команды процессора UltraSPARC II
Рис 5.22 Основные целочисленные команды процессора UltraSPARC II
Команды компьютера picoJava II
Поток управления
Поток управления — это последовательность, в которой команды выполняются динамически,
то есть во время работы программы. При отсутствии переходов и вызовов процедур команды
вызываются из последовательных ячеек памяти. Вызов процедуры влечет за собой изменение потока
управления, выполняемая в данный момент процедура останавливается, и начинается выполнение
вызванной процедуры. Сопрограммы связаны с процедурами и вызывают сходные изменения в
потоке управления. Они нужны для моделирования параллельных процессов. Ловушки (traps) и
прерывания тоже меняют поток управления при возникновении определенных ситуаций.
Последовательный поток управления и переходы
Большинство команд не меняют поток управления. После выполнения одной команды
вызывается и выполняется та команда, которая идет вслед за ней в памяти. После выполнения
каждой команды счетчик команд увеличивается на число, соответствующее длине команды. Счетчик
команд представляет собой линейную функцию от времени, которая увеличивается на среднюю
длину команды за средний промежуток времени. Иными словами, процессор выполняет команды в
том же порядке, в котором они появляются в листинге программы.
Если программа содержит переходы, то это простое соотношение между порядком
расположения команд в памяти и порядком их выполнения больше не соответствует
действительности. При наличии переходов счетчик команд больше не является монотонно
возрастающей функцией от времени.
Процедуры
Самым важным способом структурирования программ является процедура. С одной стороны,
вызов процедуры, как и команда перехода, изменяет поток управления, но в отличие от команды
перехода после выполнения задачи управление возвращается к команде, которая вызвала
процедуру.
С другой стороны, тело процедуры можно рассматривать как определение новой команды на
более высоком уровне. С этой точки зрения вызов процедуры можно считать отдельной командой,
даже если процедура очень сложная. Чтобы понять часть программы, содержащую вызов
процедуры, нужно знать, что она делает и как она это делает.
Особый интерес представляет рекурсивная процедура. Это такая процедура, которая вызывает
сама себя либо непосредственно, либо через цепочку других процедур. Изучение рекурсивных
процедур дает значительное понимание того, как реализуются вызовы процедур и что в
действительности представляют собой локальные переменные.
Для рекурсивных процедур нам нужен стек, чтобы хранить параметры и локальные
переменные для каждого вызова, как и в IJVM. Каждый раз при вызове процедуры на вершине стека
новый стековый фрейм для процедуры. Текущий фрейм — это тот фрейм, который был создан
последним. В наших примерах стек растет снизу вверх от малых адресов к большим, как и в IJVM.
Помимо указателя стека, который указывает на вершину стека, удобно иметь указатель фрейма
(FP — Frame Pointer), который указывает на фиксированное место во фрейме. Он может указывать на
связующий указатель, как в IJVM, или на первую локальную переменную.
Первое, что должна сделать процедура после того, как ее вызвали, — это сохранить
предыдущее значение FP (так, чтобы его можно было восстановить при выходе из процедуры),
скопировать значение SP (Stack Pointer – указатель стека) в FP и, возможно, увеличить на одно слово,
в зависимости от того, куда указывает FP нового фрейма.
Разные машины оперируют с указателем фрейма немного поразному, иногда помещая его в
самый низ стекового фрейма, иногда — в вершину, а иногда — в середину, но в любом случае
обязательно должна быть возможность выйти из процедуры и восстановить предыдущее состояние
стека.
Код, который сохраняет старый указатель фрейма, устанавливает новый указатель фрейма и
увеличивает указатель стека, чтобы зарезервировать пространство для локальных переменных,
называется прологом процедуры. При выходе из процедуры стек должен быть очищен, и этот
процесс называется эпилогом процедуры. Одна из важнейших характеристик компьютера —
насколько быстро он может совершать пролог и эпилог. Если они очень длинные и выполняются
медленно, делать вызовы процедур будет невыгодно.
Сопрограммы
В обычной последовательности вызовов существует четкое различие между вызывающей
процедурой и вызываемой процедурой. Рассмотрим процедуру А, которая вызывает процедуру В.
Процедура В работает какое-то время, затем возвращается к А. На первый взгляд может
показаться, что эти ситуации симметричны, поскольку ни А, ни В не являются главной программой.
Различие состоит в том, что когда управление переходит от А к В, процедура В начинает выполняться
с самого начала; а при переходе из В обратно в А выполнение начинается не с начала процедуры А, а
с того момента, за которым последовал вызов процедуры В.
Это различие отражается в способе передачи управления между А и В. Когда А вызывает В, она
использует команду вызова процедуры, которая помещает адрес возврата (то есть адрес того
выражения, которое последует за процедурой) в такое место, откуда его потом легко будет
вытащить, например в вершину стека. Затем она помещает адрес процедуры В в счетчик команд,
чтобы завершить вызов. Для выхода из процедуры В используется не команда вызова процедуры, а
команда выхода из процедуры, которая просто выталкивает адрес возврата из стека и помещает его
в счетчик команд.
Иногда нужно иметь две процедуры А и В, каждая из которых вызывает другую в качестве
процедуры. При возврате из В к А процедура В совершает переход к тому оператору, за которым
последовал вызов процедуры В. Когда процедура А передает управление процедуре В, она
возвращается не к самому началу В (за исключением первого раза), а к тому месту, на котором
произошел предыдущий вызов А. Две процедуры, работающие подобным образом, называются
сопрограммами.
Сопрограммы обычно используются для того, чтобы производить параллельную обработку
данных на одном процессоре. Каждая сопрограмма работает как бы параллельно с другими
сопрограммами, как будто у нее есть собственный процессор.
Ловушки
Ловушка (trap) — это особый тип вызова процедуры, который происходит при определенном
условии. Обычно это очень важное, но редко встречающееся условие. Один из примеров такого
условия — переполнение. В большинстве процессоров, если результат выполнения арифметической
операции превышает самое большое допустимое число, срабатывает ловушка. Это значит, что поток
управления переходит в какую-то фиксированную ячейку памяти, а не продолжается
последовательно дальше. В этой фиксированной ячейке находится команда перехода к специальной
процедуре (обработчику системных прерываний), которая выполняет какое-либо определенное
действие, например печатает сообщение об ошибке. Если результат операции находится в пределах
допустимого, ловушка не задействуется.
Важно то, что этот вид прерывания вызывается каким-то исключительным условием,
вызванным самой программой и обнаруженным аппаратным обеспечением или микропрограммой.
Ловушку можно реализовать путем открытой проверки, выполняемой микропрограммой (или
аппаратным обеспечением). Если обнаружено переполнение, адрес ловушки загружается в счетчик
команд. То, что является ловушкой на одном уровне, может находиться под контролем программы
на более низком уровне.
Наиболее распространенные условия, которые могут вызывать ловушки, — это переполнение и
исчезновение значащих разрядов при операциях с плавающей точкой, переполнение при операциях
с целыми числами, нарушения защиты, неопределяемый код операции, переполнение стека,
попытка запустить несуществующее устройство ввода-вывода, попытка вызвать слово из ячейки с
нечетным адресом и деление на 0.
Прерывания
Прерывания — это изменения в потоке управления, вызванные не самой программой, а чемлибо другим и обычно связанные с процессом ввода-вывода. Например, программа может
приказать диску начать передачу информации и заставить диск произвести прерывание, как только
передача данных завершится. Как и ловушка, прерывание останавливает работу программы и
передает управление программе обработки прерываний, которая выполняет какое-то определенное
действие. После завершения этого действия программа обработки прерываний передает управление
прерванной программе.
Различие между ловушками и прерываниями в следующем: ловушки синхронны с
программой, а прерывания асинхронны. Если программа перезапускается много раз с одним и тем
же материалом на входе, ловушки каждый раз будут происходить в одном и том же месте, а
прерывания могут меняться в зависимости от того, в какой момент человек нажимает возврат
каретки. Причина воспроизводимости ловушек и невоспроизводимости прерываний состоит в том,
что первые вызываются непосредственно самой программой, а прерывания вызываются программой
косвенно.
С прерываниями связано важное понятие прозрачности. Когда происходит прерывание,
производятся какие-либо действия и запускаются какие-либо программы, но когда все закончено,
компьютер должен вернуться точно в то же состояние, в котором он находился до прерывания.
Программа обработки прерываний, обладающая таким свойством, называется прозрачной.