Читайте также:
|
|
Типы команд
Команды можно грубо поделить на несколько групп, которые, хотя и могут различаться в деталях, воспроизводятся практически в каждой машине. Кроме того, в каждом компьютере всегда имеется несколько необычных команд, добавленных либо в целях совместимости с предыдущими моделями, либо потому, что у разработчика возникла блестящая идея, либо по требованию правительства, заплатившего производителю, чтобы тот включил в набор команд новую команду. В этом разделе мы попытаемся описать все наиболее распространенные категории, однако отметим, что мы не претендуем на исчерпывающее изложение.
Команды перемещения данных
Копирование данных из одного места в другое - одна из самых распространенных операций. Под копированием мы понимаем создание нового объекта с точно таким же набором битов, как у исходного. Такое понимание слова "перемещение" несколько отличается от его обычного значения. Если мы говорим, что какой-то человек переместился из Нью-Йорка в Калифорнию, это не значит, что в Калифорнии была создана идентичная копия этого человека, а оригинал остался в Нью-Йорке. Когда мы говорим, что содержимое ячейки памяти 2000 переместилось в какой-либо регистр, мы всегда подразумеваем, что там была создана идентичная копия, а оригинал все еще находится в ячейке 2000. Команды перемещения данных лучше было бы назвать командами дублирования данных, но название уже устоялось.
Есть две причины, по которым данные могут копироваться из одного места в другое. Одна из них фундаментальна: присваивание переменным значений. Следующая операция присваивания выполняется путем копирования значения, которое находится в ячейке памяти с адресом В, в ячейку А, поскольку программист дал команду это сделать:
А = В
Вторая причина копирования данных - предоставить возможность быстрого обращения к ним. Как мы уже видели, многие команды могут обращаться к переменным только в том случае, если они находятся в регистре. Поскольку существует два возможных источника данных (память и регистр) и два возможных приемника данных (память и регистр), существует также 4 различных способа копирования. В одних компьютерах для этих четырех случаев поддерживаются 4 команды, в других - единственная команда. Некоторые компьютеры используют команду LOAD для загрузки данных из памяти в регистр, команду STORE для сохранения в памяти данных из регистра, команду MOVE для перемещения данных из одного регистра в другой, но вообще не имеют команд для копирования из одной части памяти в другую.
Команды перемещения данных должны как-то указывать, сколько данных нужно переместить. Существуют команды для перемещения разных объемов данных - от одного бита до всей памяти. В машинах с фиксированной длиной слова обычно перемещается ровно одно слово. Любые перемещения других объемов данных (больше или меньше слова) должны выполняться программно с использованием операций сдвига и слияния. Некоторые архитектуры команд дают возможность копировать фрагменты данных размером меньше слова (они обычно измеряются в байтах), а также сразу нескольких слов. Копирование нескольких слов рискованно, особенно если максимальное количество слов достаточно большое, поскольку такая операция может занять много времени и существует вероятность ее прерывания в середине. Некоторые машины с переменной длиной слов содержат команды, которые определяют только адреса источника и приемника, но не объем данных - в этом случае перемещение продолжается до тех пор, пока не встретится специальное поле, идентифицирующее окончание данных.
Бинарные операции
Бинарные операции - это такие операции, которые получают результат на основе значений двух операндов. Все архитектуры команд содержат команды для сложения и вычитания целых чисел. Кроме того, практически во всех архитектурах имеются команды умножения и деления целых чисел. Вероятно, нет необходимости объяснять, почему компьютеры оснащены арифметическими командами.
В следующую группу бинарных операций входят булевы команды. Хотя существует 16 булевых функций от двух переменных, команды для всех 16 поддерживаются в очень немногих машинах. Обычно поддерживаются только операции И, ИЛИ и НЕ; иногда кроме них еще ИСКЛЮЧАЮЩЕЕ ИЛИ, НЕ-ИЛИ и НЕ-И.
Унарные операции
В унарных операциях результат получается обработкой единственного операнда. Поскольку в данном случае нужно задавать на один адрес меньше, чем в бинарных операциях, команды иногда бывают короче, если только не требуется задавать другую информацию.
Очень полезны команды сдвига и циклического сдвига. Они часто даются в нескольких вариантах. Сдвиги - это операции, при которых биты сдвигаются влево или вправо, при этом биты, которые сдвигаются за пределы слова, утрачиваются. Циклические сдвиги - это сдвиги, при которых биты, вытесненные с одного конца, появляются на другом конце слова. Разницу между обычным сдвигом и циклическим сдвигом иллюстрирует следующий пример:
А:00000000 00000000 00000000 01110011 Сдвиг А вправо на 2 бита:
00000000 00000000 00000000 00011100 Циклический сдвиг А вправо на 2 бита:
11000000 00000000 00000000 00011100
Обычные и циклические сдвиги влево и вправо играют очень важную роль. Если я-разрядное слово циклически сдвигается влево на к бит, результат будет такой же, как при циклическом сдвиге вправо на п - к бит.
Сдвиги вправо часто выполняются с расширением по знаку. Это значит, что позиции, освободившиеся на левом конце слова, заполняются знаковым битом (О или 1) исходного слова, как будто знаковый бит перетаскивают вправо. Кроме того, это значит, что отрицательное число остается отрицательным. Вот как выглядят сдвиги на 2 бита вправо:
А:
1111111 11111111 11111111 11110000 А сдвигается без знакового расширения:
0011111 11111111 11111111 11111100 А сдвигается со знаковым расширением:
1111111 11111111 11111111 11111100
Операция сдвига используется при умножении и делении на 2. Если положительное целое число сдвигается влево на & бит, результатом будет исходное число, умноженное на 2к. Если положительное целое число сдвигается вправо на & бит, результатом становится исходное число, деленное на 2к.
Сдвиги могут использоваться для повышения скорости выполнения некоторых арифметических операций. Рассмотрим выражение 18 х п, где п - положительное целое число: 18 х я = 16хгг + 2хгг. Значение 16 х п можно получить путем сдвига копии п на 4 бита влево. Значение 2 х п можно получить, сдвинув п на 1 бит влево. Сумма этих двух чисел равна 18 х гг. Таким образом, целиком произведение можно вычислить путем одного перемещения, двух сдвигов и одного сложения, что обычно выполняется гораздо быстрее, чем операция умножения. Конечно, компилятор может применить такую схему, только если один из множителей является константой.
Сдвиг отрицательных чисел даже со знаковым расширением дает совершенно другие результаты. Рассмотрим, например, число -1 в обратном двоичном коде. При сдвиге влево на 1 бит получается число -3. При сдвиге влево еще на 1 бит получается число -7:
Число -1 в обратном двоичном коде:
11111111 11111111 11111111 11111110 Число -1 сдвигается влево на 1 бит (-3):
11111111 11111111 11111111 11111100 Число -1 сдвигается влево на 2 бита (-7):
11111111 11111111 11111111 11111000
Сдвиг влево отрицательных чисел в обратном двоичном коде не ведет к умножению числа на 2. В то же время сдвиг вправо корректно обеспечивает деление.
А теперь рассмотрим число -1 в дополнительном двоичном коде. При сдвиге вправо на 6 бит с расширением по знаку получается число -1, что неверно, поскольку целая часть от -1/64 равна 0:
Число -1 в дополнительном двоичном коде:
11111111 11111111 11111111 11111111 Число -1, сдвинутое влево на 6 бит, равно -1:
11111111 11111111 11111111 11111111
Как правило, сдвиг вправо вызывает ошибки, поскольку он производит округление до большего отрицательного целого числа, что недопустимо при выполнении целочисленных арифметических операций с отрицательными числами. В то же время сдвиг влево аналогичен умножению на 2.
Операции циклического сдвига нужны для манипулирования последовательностями битов в словах. Чтобы проверить все биты в слове, каждый бит путем циклического сдвига слова поочередно помещается в знаковый разряд, где его можно легко проверить; когда все биты будут проверены, восстанавливается изначальное значение слова. Операции циклического сдвига гораздо удобнее операций обычного сдвига, поскольку при этом не теряется информация: произвольная операция циклического сдвига может быть отменена операцией циклического сдвига в противоположную сторону.
В некоторых бинарных операциях обычно используются совершенно определенные операнды, поэтому для быстрого выполнения аналогичных действий в архитектуры команд часто включаются унарные операции. Например, в начале вычислений чрезвычайно часто требуется перемещение нуля в память или в регистр. Перемещение нуля - это особый случай команды перемещения данных. Поэтому для повышения производительности в архитектуру вводится операция CLR с единственным операндом - адресом той ячейки, которую нужно очистить (то есть установить на 0).
Прибавление 1 к слову тоже часто требуется при различных подсчетах. Унарная форма команды ADD - это операция инкремента INC. Другой пример - операция NEG. Отрицание X - это на самом деле бинарная операция вычитания 0 - X, но поскольку операция отрицания применяется очень часто, в архитектуру команд вводится команда NEG. Важно понимать разницу между арифметической операцией NEG и логической операцией NOT. При выполнении операции NEG происходит аддитивная инверсия числа (такое число, сумма которого с исходным числом дает 0). При выполнении операции NOT все биты в слове просто инвертируются. Эти операции очень похожи, а для системы, в которой отрицательные величины представлены в обратном двоичном коде, они идентичны. (В арифметике дополнительных кодов для выполнения команды NEG сначала инвертируются все биты, а затем к полученному результату прибавляется 1.)
Унарные и бинарные операции часто объединяются в группы по функциям, которые они выполняют, а вовсе не по числу операндов. В первую группу входят арифметические операции, в том числе операция отрицания. Во вторую группу входят логические операции и операции сдвига, поскольку эти две категории очень часто используются вместе для извлечения данных.
Сравнения и условные переходы
Практически все программы должны проверять свои данные и на основе полученных результатов изменять последовательность выполняемых команд. Рассмотрим функцию извлечения квадратного корня из числа х. Если это число отрицательно, функция должна сообщать об ошибке, если положительно - выполнять вычисления. То есть соответствующая программа должна проверять знак числа х, а затем совершать переход в зависимости от полученного результата.
Этот алгоритм можно реализовать с помощью специальных команд условного перехода, проверяющих разного рода условия и при их выполнении совершающих переходы к определенным адресам памяти. Иногда один из битов в команде указывает, нужно ли осуществлять переход в случае выполнения или в случае не выполнения условия. Часто целевой адрес является не абсолютным, а относительным (связанным с текущей командой).
Самое распространенное условие - проверка на равенство или на неравенство определенного бита нулю. Если команда проверяет знаковый бит числа и совершает переход к метке (LABEL), когда этот бит равен 1 (проверяемое число отрицательно), то выполняются те команды, которые начинаются с метки LABEL, а если этот бит равен 0 (проверяемое число положительно), то выполняются команды, следующие за командой условного перехода.
Во многих машинах имеются биты кодов условий; например, это может быть бит переполнения, который принимает значение 1 всякий раз, когда арифметическая операция выдает неправильный результат. По этому биту проверяется правильность выполнения предыдущей арифметической операции и в случае ошибки запускается программа обработки ошибок.
В некоторых процессорах есть специальный бит переноса, который принимает значение 1, если происходит перенос из самого левого бита (например, при сложении двух отрицательных чисел). Бит переноса нельзя путать с битом переполнения. Проверка бита переноса необходима для вычислений с повышенной точностью (то есть когда целое число представлено двумя или более словами).
Проверка на ноль очень важна при выполнении циклов и в некоторых других случаях. Если бы все команды условного перехода проверяли только 1 бит, то тогда для проверки определенного слова на равенство 0 нужно было бы поочередно проверять каждый бит, чтобы убедиться, что ни один из них не равен 1. Чтобы избежать подобной ситуации, многие машины поддерживают команду, которая проверяет слово целиком и выполняет переход, если оно равно 0. Конечно же, в этом решении все равно проверяется каждый бит, просто ответственность за проверку перекладывается на микроархитектуру. На практике в число устройств обычно включается регистр, все биты которого соединяются операцией ИЛИ, чтобы получить в результате бит, показывающий, имеются ли в регистре единичные биты. Бит Z на рис. 4.1 обычно вычисляется следующим образом: сначала все выходные биты АЛУ соединяются операцией ИЛИ, а затем полученный результат инвертируется.
Операция сравнения слов или символов очень важна, например, при сортировке. Чтобы выполнить сравнение, требуется три адреса: два нужны для данных, а по третьему адресу будет совершаться переход в случае выполнения условия. В тех компьютерах, где форматы команд позволяют иметь три адреса в команде, проблем не возникает. Но если такие форматы не предусмотрены, нужно что-то делать, чтобы обойти проблему.
Одно из возможных решений - ввести команду, которая выполняет сравнение и записывает результат в виде одного или нескольких битов условий. Следующая команда может проверять биты условия и совершать переход, если два сравниваемых значения были равны, или если они были неравны, или если первое из них было больше второго и т. д. Такой подход применяется в Pentium 4 и UltraSPARC III.
В операции сравнения двух чисел есть некоторые тонкости. Сравнение - это не такая простая операция, как вычитание. Если очень большое положительное число сравнивается с очень большим отрицательным числом, операция вычитания приведет к переполнению, поскольку результат вычитания будет невозможно представить. Тем не менее команда сравнения и в этом случае должна определить, удовлетворяется ли условие, и возвратить правильный ответ. При сравнении переполнения возникать не должно.
Кроме того, при сравнении чисел нужно решить, считать ли числа числами со знаком или числами без знака. 3-разрядные бинарные числа можно упорядочить двумя способами. От самого маленького к самому большому:
♦ Без знака: ООО, 001, 010, 011, 100, 101, 110, 111.
♦ Со знаком: 100, 101, 110, 111, 000, 001, 010, 011.
В первой строке по возрастанию перечислены положительные числа от 0 до 7, во второй - целые числа со знаком от -4 до +3 в дополнительном двоичном коде (тоже по возрастанию). Ответ на вопрос: "Какое число больше: ОН или 100?" - зависит от того, считаются ли числа числами со знаком.
Команды вызова процедур
Процедурой называют группу команд, которая решает определенную задачу и которую можно вызывать из разных мест программы. Вместо термина "процедура" часто используется термин "подпрограмма", особенно когда речь идет о программах на языке ассемблера. Когда процедура заканчивает решение задачи, она должна вернуться к оператору, расположенному в программе следом за оператором вызова процедуры. Следовательно, адрес возврата должен как-то передаваться процедуре или сохраняться где-либо таким образом, чтобы можно было определить, куда возвращаться после решения задачи.
Адрес возврата может помещаться в одном из трех мест: в памяти, в регистре или в стеке. Самое худшее решение - поместить этот адрес в фиксированную ячейку памяти. Тогда, если процедура вызовет другую процедуру, второй вызов приведет к потере первого адреса возврата.
Более удачное решение - сохранить адрес возврата в первом слове процедуры, а первой выполняемой командой сделать второе слово процедуры. После завершения процедуры будет происходить переход к первому слову, а если аппа-ратно в первом слове наряду с адресом возврата предоставить код операции, произойдет непосредственный переход к этой операции. Процедура может вызывать другие процедуры, поскольку в каждой процедуре имеется пространство для одного адреса возврата. Но если процедура вызывает сама себя, эта схема не сработает, поскольку первый адрес возврата будет уничтожен вторым вызовом. (Способность процедуры вызывать саму себя, называемая рекурсией, очень важна и теоретически, и практически.) Более того, если процедура А вызывает процедуру J3, процедура В вызывает процедуру С, а процедура С вызывает процедуру А (непосредственная, или цепочечная, рекурсия), эта схема сохранения адреса возврата также не сработает.
Еще более удачное решение - поместить адрес возврата в регистр. Тогда если процедура рекурсивна, ей придется помещать адрес возврата в другое место каждый раз, когда она вызывается.
Самое лучшее решение - поместить адрес возврата в стек. Тогда при завершении процедуры, она должны выталкивать адрес возврата из стека. При такой форме вызова процедур рекурсия не порождает никаких проблем; адрес возврата будет автоматически сохраняться таким образом, чтобы не уничтожить предыдущий адрес возврата. Мы рассматривали такой способ сохранения адреса возврата в машине IJVM
Управление циклами
Часто возникает необходимость выполнять некоторую группу команд фиксированное количество раз, поэтому некоторые машины для управления этим процессом содержат специальные команды. Во всех схемах подобного рода имеется счетчик, который увеличивается или уменьшается на какую-либо константу каждый раз при выполнении цикла. Кроме того, этот счетчик каждый раз проверяется. В случае удовлетворения проверяемого условия цикл завершается.
Счетчик запускается вне цикла, и затем сразу начинается выполнение цикла. Последняя команда цикла обновляет счетчик, и, если условие завершения цикла еще не удовлетворено, происходит возврат к первой команде цикла. Если условие удовлетворено, цикл завершается и начинается выполнение команды, расположенной сразу после цикла. Цикл такого типа с проверкой в конце цикла представлен в листинге 5.3. (Мы не могли здесь использовать язык Java, поскольку в нем нет оператора goto).
Листинг 5.3. Цикл с проверкой в конце
i = 1;
L1: первый оператор;
последний оператор;
i = i + 1;
if (i<n) goto LI;
Цикл такого типа всегда будет выполнен хотя бы один раз, даже если п < 0. Представим программу, которая поддерживает базу данных о персонале компании. В определенном месте программа начинает считывать информацию о конкретном работнике. Она считывает число п - количество детей у работника, и выполняет цикл п раз, по одному разу на каждого ребенка. В цикле она считывает его имя, пол и дату рождения, так что компания сможет послать ему (или ей) подарок точно в срок. Однако если у работника нет детей и значение п равно 0, цикл все равно будет выполнен один раз, что даст ошибочные результаты.
В листинге 5.4 представлен другой способ проверки, который дает правильные результаты даже при п < 0. Отметим, что если и увеличение счетчика, и проверка условия выполняются в одной команде, разработчикам придется выбирать один из двух методов.
Листинг 5.4. Цикл с проверкой в начале
i = 1;
LI: if(i>n) goto L2; первый оператор;
последний оператор;
1=1+1;
goto L1;
L2:
Рассмотрим код, который породит компилятор при обработке следующей строки:
for (1=0; 1<n; 1++) { операторы }
Если у компилятора нет никакой информации о числе п, ему, чтобы корректно обработать случай п < 0, придется действовать так, как показано в листинге 5.4. Однако если компилятор определит, что п > 0 (например, проверив, какое значение присвоено п), он сможет использовать более эффективный код, представленный в листинге 5.3. Когда-то в стандарте языка FORTRAN требовалось, чтобы все циклы выполнялись хотя бы один раз. Это позволяло всегда порождать более эффективный код (как в листинге 5.3). В 1977 году этот дефект был исправлен, поскольку даже приверженцы языка FORTRAN начали осознавать, что не слишком хорошо иметь оператор цикла с такой странной семантикой, хотя она и позволяет экономить одну команду перехода на каждом цикле.
Команды ввода-вывода
Ни одна другая группа команд не различается в разных машинах так сильно, как команды ввода-вывода. В современных персональных компьютерах используются три разные схемы ввода-вывода:
+ программируемый ввод-вывод с активным ожиданием;
+ ввод-вывод с управлением по прерываниям;
+ ввод-вывод с прямым доступом к памяти.
Мы рассмотрим каждую из этих схем по очереди.
Самым простым методом ввода-вывода является программируемый ввод-вывод. Эта схема часто используется в дешевых микропроцессорах, например во встроенных системах или в таких системах, которые должны быстро реагировать на внешние изменения (системы реального времени). Подобные процессоры обычно имеют одну команду ввода и одну команду вывода. Каждая из этих команд выбирает одно из устройств ввода-вывода. Между фиксированным регистром процессора и выбранным устройством ввода-вывода передается по одному символу. Процессор должен выполнять определенную последовательность команд при каждом считывании и записи символа.
Основной недостаток программируемого ввода-вывода заключается в том, что центральный процессор проводит большую часть времени в цикле, ожидая готовности устройства. Этот процесс называется активным ожиданием. Если центральному процессору больше ничего не нужно делать (как, например, в стиральной машине), в этом нет ничего страшного (хотя даже простому контроллеру часто нужно контролировать несколько параллельных процессов). Но если процессору приходится выполнять еще какие-либо действия, например запускать другие программы, то активное ожидание здесь не подходит и нужно искать другие методы ввода-вывода.
Чтобы избавиться от активного ожидания, необходимо, чтобы центральный процессор запускал устройство ввода-вывода, а это устройство после завершения своей работы сообщало об этом процессору с помощью прерывания. Посмотрите на рис. 5.20. Установив бит разрешения прерываний в регистре устройства, программа говорит о том, что ждет от аппаратуры сигнала о завершении работы устройства ввода-вывода. Подробнее мы рассмотрим прерывания далее в этой главе, когда перейдем к вопросам передачи управления.
Во многих компьютерах сигнал прерывания порождается путем логического умножения (И) бита разрешения прерываний и бита готовности устройства. Если в первую очередь (перед запуском устройства ввода-вывода) программно разрешить прерывания, прерывание произойдет сразу же, поскольку бит готовности уже установлен. То есть вероятно сначала нужно запустить устройство, а уже после этого разрешить прерывания. Запись байта в регистр состояния устройства не меняет бита готовности, который может только считываться.
Ввод-вывод с управлением по прерываниям - это большой шаг вперед по сравнению с программируемым вводом-выводом, но все же он далеко не совершенен. Дело в том, что прерывание приходится генерировать для каждого передаваемого символа и нужно каким-то образом избавляться от слишком большого числа прерываний.
Решение лежит в возвращении к программируемому вводу-выводу, но только эту работу вместо центрального процессора должен делать кто-то другой. Отметим, что если какое-нибудь высокоскоростное устройство, например диск, будет запускаться контроллером ПДП, потребуется очень много циклов шины и для обращений к памяти, и для обращений к устройству. Во время этих циклов центральному процессору придется ждать (контроллер ПДП всегда имеет приоритет перед ЦП на доступ к шине, поскольку устройства ввода-вывода обычно не допускают задержек) - этот процесс называется захватом цикла памяти. Однако издержки, связанные с захватом циклов, не идут ни в какое сравнение с экономией, получаемой благодаря тому, что при передаче каждого байта (слова) не нужно обрабатывать прерывание.
Сравнение наборов команд
Рассмотренные наборы команд разительно отличаются друг от друга. 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 набора регистров для ускоренной обработки прерываний, возможность доступа к регистрам в пространстве памяти и на удивление мощные команды побитовой обработки. Основное преимущество такого решения состоит в том, что оно реализуется на незначительном числе транзисторов. Отсюда экономия пространства при размещении на кристалле, а значит, снижение стоимости процессора.
Дата добавления: 2015-07-08; просмотров: 163 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Режимы адресации в командах перехода | | | ШЕТ ТІЛДЕР ЖӘНЕ ІСКЕРЛІК КАРЬЕРА УНИВЕРСИТЕТІ |