Boroda_2
.docОбщий обзор уровня архитектуры набора команд
Давайте начнем изучение уровня архитектуры набора команд с вопроса о том, что он собой представляет. Этот вопрос на первый взгляд может показаться простым, но на самом деле здесь очень много тонкостей. В следующем подразделе мы обсудим некоторые из них, а затем мы рассмотрим модели памяти, регистров и команд.
Свойства уровня архитектуры набора команд
В принципе уровень архитектуры набора команд — это тот уровень, на котором компьютер представляется программисту, пишущему программы на машинном языке. Поскольку сейчас ни один нормальный программист таких программ не пишет, мы слегка переделали это определение: программа уровня архитектуры набора команд — это то, что получается в результате работы компилятора (в данный момент мы будем касаться системных вызовов и символического языка ассемблера). Чтобы получить программу уровня архитектуры набора команд, создатель компилятора должен знать, какая модель памяти используется в машине, какие регистры, типы данных и команды имеются в наличии, и т. д. Вся эта информация в совокупности и определяет уровень архитектуры набора команд.
В соответствии с этим определением вопросы о том, доступна ли микроархитектура программно, конвейеризирован ли компьютер, является ли он суперска- лярным и т. д., не относятся к уровню архитектуры набора команд, поскольку разработчик компилятора всего этого не видит. Однако это замечание не совсем справедливо, поскольку некоторые из этих характеристик влияют на производительность, а производительность, в свою очередь, — вполне доступный для разработчика компилятора показатель. Рассмотрим, например, суперскалярную машину, которая в одном цикле может обрабатывать сдвоенные команды, причем такие, что одна команда целочисленная, а вторая с плавающей точкой. Если в коде, полученном в результате работы компилятора, целочисленные команды и команды с плавающей точкой будут чередоваться, то производительность заметно повысится. Таким образом, детали суперскалярной операции доступны на уровне архитектуры набора команд, то есть границы между разными уровнями размыты.
Для одних архитектур уровень команд определяется формальным документом, который обычно выпускается промышленным консорциумом, для других — нет. Например, системы V9 SPARC (Version 9 SPARC) и JVM имеют официальные определения [216]. Цель такого официального документа — дать возможность различным производителям выпускать машины данного типа, чтобы эти машины могли выполнять одни и те же программы и получать при этом одни и те же результаты.
В случае с системой SPARC подобные документы нужны для того, чтобы различные предприятия могли выпускать идентичные микросхемы SPARC, отличающиеся друг от друга только по производительности и цене. Чтобы эта идея работала, поставщики микросхем должны знать, что делает микросхема SPARC (на уровне архитектуры набора команд). Следовательно, в документе говорится о том, какова модель памяти, какие имеются регистры, какие действия выполняют команды и т. д., а не о том, что представляет собой микроархитектура.
В таких документах содержатся нормативные разделы, в которых излагаются требования, и информативные разделы, призванные помочь читателю, но не являющиеся частью формального определения. В нормативных разделах постоянно встречаются такие слова, как должно быть, нельзя, следует, означающие, соответственно, требование, запрет и рекомендацию. Например, следующее предложение означает, что если программа выполняет код операции, который не определен, он должен вызывать системное прерывание, а не просто игнорироваться:
Выполнение зарезервированного кода операции должно вызывать системное
прерывание.
Может быть и альтернативный подход:
Результат выполнения зарезервированного кода операции определяется реализацией.
Это значит, что составитель компилятора не может просчитать какие-то конкретные действия предоставляет конструкторам свободу выбора. К описанию архитектуры часто прилагаются тестовые пакеты для проверки, действительно ли данная реализация соответствует техническим требованиям.
Совершенно ясно, почему система V9 SPARC поставляется с документом, в котором определяется уровень архитектуры набора команд, — это нужно для того, чтобы все микросхемы V9 SPARC могли выполнять одни и те же программы. Для уровня архитектуры набора команд процессора Pentium 4 такого документа нет, поскольку компания Intel не хочет, чтобы другие производители могли выпускать микросхемы Pentium 4. Компания Intel даже обращалась в суд, чтобы запретить производство своих микросхем другим производителям, но проиграла процесс.
Другое важное качество уровня архитектуры набора команд состоит в том, что в большинстве машин поддерживаются по крайней мере два режима. В привилегированном режиме запускается операционная система. Этот режим позволяет выполнять все команды. Пользовательский режим предназначен для запуска прикладных программ. Он не позволяет выполнять некоторые потенциально опасные команды (например, те, которые непосредственно манипулируют кэш-памятью). В этой главе мы в первую очередь сосредоточимся на командах и свойствах пользовательского режима.
Модели памяти
Во всех компьютерах память разделена на ячейки, которые имеют последовательные адреса. В настоящее время наиболее распространенный размер ячейки — 8 бит, но раньше использовались ячейки от 1 до 60 бит (см. табл. 2.1). Ячейка из 8 бит называется байтом. Причиной применения именно 8-разрядных ячеек памяти является ASCII-символ, который занимает 7 бит, а вместе с битом четности — 8. Если в будущем будет доминировать кодировка UNICODE, то ячейки памяти, возможно, станут 16-разрядными. Вообще говоря, число 24 лучше, чем 23, поскольку 4 — степень двойки, а 3 — нет.
Байты обычно группируются в 4-байтные (32-разрядные) или 8-байтные (64-разрядные) слова с командами манипулирования целыми словами. Многие архитектуры требуют, чтобы слова были выровнены в своих естественных границах. Так, 4-байтное слово может начинаться с адреса 0, 4, 8 и т. д., но не с адреса 1 или 2. Точно так же слово из 8 байт может начинаться с адреса 0, 8 или 16, но не с адреса 4 или 6. Механизм размещения 8-байтных слов в памяти иллюстрирует рис. 5.2.
Выравнивание адресов требуется довольно часто, поскольку при этом память работает наиболее эффективно. Например, процессор Pentium 4, который вызывает из памяти по 8 байт за обращение, использует 36-разрядные физические адреса, но содержит только 33 адресных бита. Следовательно, Pentium 4 даже не сможет обратиться к невыровненной памяти, поскольку младшие 3 бита явным образом не определены. Эти биты всегда равны 0, и все адреса памяти кратны значению 8 байт.
все адресное пространство целиком дублируется, и считывание из любого адреса вызывает разные результаты в зависимости от того, что именно считывается: слово данных или команда. При разделенной кэш-памяти существует только одно адресное пространство, просто в разных блоках кэш-памяти хранятся разные части этого пространства.
Еще один аспект модели памяти — семантика памяти. Естественно ожидать, что команда LOAD, если она выполняется после команды STORE, обратится к тому же адресу и возвратит только что сохраненное значение. Однако, как мы видели в главе 4, во многих машинах микрокоманды переупорядочиваются. Таким образом, существует реальная опасность, что память будет работать не так, как ожидается. Ситуация усложняется при наличии мультипроцессора, когда каждый процессор посылает в общую память поток запросов на чтение и запись, и эти запросы тоже могут быть переупорядочены.
Системные разработчики могут применять один из нескольких подходов решения этой проблемы. С одной стороны, все запросы к памяти могут быть упорядочены таким образом, чтобы каждый из них завершался до того, как начнется следующий. Такая стратегия отрицательно сказывается на производительности, но зато дает простейшую семантику памяти (все операции выполняются строго в том порядке, в котором они расположены в программе).
С другой стороны, можно вообще не давать никаких гарантий относительно упорядоченности запросов к памяти, а чтобы добиться такой упорядоченности, программа выполняет команду SYNC, которая блокирует запуск всех новых операций с памятью до тех пор, пока не завершатся предыдущие. Эта идея весьма затрудняет работу создателей компиляторов, поскольку им приходится тщательно разбираться в том, как работает соответствующая микроархитектура, но зато разработчикам аппаратного обеспечения предоставлена полная свобода в плане оптимизации использования памяти.
Возможны также промежуточные модели памяти, в которых аппаратное обеспечение автоматически блокирует запуск определенных операций с памятью (например, тех, которые связаны с RAW- и WAR-взаимозависимостями), при этом запуск всех других операций не блокируется. Хотя реализация этих возможностей на уровне архитектуры набора команд довольно утомительна (по крайней мере, для создателей компиляторов и программистов на языке ассемблера), сейчас заметна тенденция к преобладанию подобного подхода. Данная тенденция вызвана к жизни такими разработками, как механизмы переупорядочения микрокоманд, конвейеры, многоуровневая кэш-память и т. д. Другие, менее известные, примеры такого рода мы рассмотрим в этой главе чуть позже.
Регистры
Во всех компьютерах имеются несколько регистров, доступных на уровне архитектуры набора команд. Они позволяют контролировать выполнение программы, хранить временные результаты, а также служат для некоторых других целей. Обычно регистры, доступные на уровне микроархитектуры, например TOS и MAR (см. рис. 4.1), на уровне архитектуры набора команд недоступны, однако некоторые регистры, например счетчик команд и указатель стека, доступны на обоих уровнях. В то же время регистры, доступные на уровне архитектуры набора команд, всегда доступны на уровне микроархитектуры, поскольку именно там они реализованы.
Регистры уровня архитектуры набора команд можно разделить на две категории: специальные регистры и регистры общего назначения. К специальным регистрам относятся счетчик команд и указатель стека, а также другие регистры, имеющие особые функции. Регистры общего назначения содержат ключевые локальные переменные и промежуточные результаты вычислений. Их основная функция состоит в том, чтобы обеспечить быстрый доступ к часто используемым данным (обычно без обращений к памяти). RISC-машины с высокоскоростными процессорами и (относительно) медленной памятью обычно содержат как минимум 32 регистра общего назначения, причем в новых процессорах количество регистров общего назначения постоянно растет.
В некоторых машинах регистры общего назначения полностью симметричны и взаимозаменяемы. Если все регистры эквивалентны, для хранения промежуточного результата компилятор может использовать как регистр R1, так и регистр R25. Выбор регистра не имеет никакого значения.
В других машинах некоторые регистры общего назначения могут быть специализированными. Например, в процессоре Pentium 4 имеется регистр EDX, который может использоваться в качестве регистра общего назначения, но который, кроме того, используется для решения сугубо специфических задач (получает половину произведения при умножении и половину делимого при делении).
Даже если регистры общего назначения полностью взаимозаменяемы, операционная система или компиляторы часто следуют соглашениям о том, каким образом использовать эти регистры. Например, некоторые регистры могли бы применяться для хранения параметров вызываемых процедур, другие выполнять роль временных. Если компилятор помещает важную локальную переменную в регистр R1, а затем вызывает библиотечную процедуру, которая воспринимает R1 как регистр, временно выделенный в ее распоряжение, то после возвращения значения этой процедурой в регистре R1 может остаться «мусор». То есть если существуют какие-либо системные соглашения по поводу того, как нужно использовать регистры, разработчики компиляторов и программисты, пишущие на ассемблере, должны им следовать.
Помимо регистров, доступных на уровне архитектуры набора команд, всегда существует довольно много специальных регистров, доступных только в привилегированном режиме. Эти регистры контролируют различные блоки кэш-памяти, основную память, устройства ввода-вывода, другие устройства машины. Данные регистры используются только операционной системой, поэтому компиляторам и пользователям не обязательно знать об их существовании.
Существует один регистр, который представляет собой «гибрид», доступный и в привилегированном, и в пользовательском режимах. Это — упоминавшийся в главе 4 регистр PSW (Program State Word — слово состояния программы), который еще называют флаговым. Флаговый регистр содержит различные биты, необходимые центральному процессору. Самые важные биты — это коды условий. Они устанавливаются в каждом цикле АЛУ и отражают состояние результата предыдущей операции:
+ N — результат отрицателен (Negative);
+ Z — результат равен 0 (Zero);
+ V — результат вызвал переполнение (oVerflow);
+ С — перенос самого левого бита (Carry out);
♦ А — перенос бита 3 (Auxiliary carry — служебный перенос);
+ Р — результат четный (Parity).
Коды условий очень важны, поскольку используются при сравнениях и условных переходах. Например, команда СМР обычно вычитает один операнд из другого и на основе полученной разности устанавливает коды условий. Если операнды равны, то разность будет равна 0, а во флаговом регистре установится бит Z. Последующая команда BEQ (Branch Equal — переход в случае равенства) проверяет бит Z и совершает переход, если он установлен.
Флаговый регистр может хранить не только коды условий. Его содержимое в разных машинах может быть разным. Дополнительные поля указывают режим машины (например, пользовательский или привилегированный), бит трассировки (который используется для отладки), уровень приоритета процессора, статус разрешения прерываний. Флаговый регистр обычно читается в пользовательском режиме, но некоторые поля могут записываться только в привилегированном режиме (например, бит, который указывает режим).
Команды
Главная особенность уровня, который мы сейчас рассматриваем, — это набор машинных команд. Они управляют действиями машины. В этом наборе всегда в той или иной форме присутствуют команды LOAD и STORE, предназначенные для перемещения данных между памятью и регистрами, и команда MOVE, которая служит для копирования данных из одного регистра в другой. Также всегда присутствуют арифметические и логические команды, команды сравнения элементов данных и команды переходов в зависимости от результатов. Некоторые типичные команды мы уже рассматривали в главе 4 (см. табл. 4.2.), а в этой главе мы познакомимся со многими другими.
В этой главе мы обсудим три совершенно разные архитектуры команд: IA-32 компании Intel (она реализована в Pentium 4), Version 9 SPARC (она реализована в процессорах UltraSPARC) и 8051. Мы не преследуем цель дать исчерпывающее описание каждой из этих архитектур. Мы просто хотим продемонстрировать важные аспекты архитектуры команд и показать, как эти аспекты меняются от одной архитектуры к другой. Начнем с машины Pentium 4.
Общий обзор уровня архитектуры набора команд Pentium 4
Процессор Pentium 4 развивался на протяжении многих лет. Как отмечалось в главе 1, его история восходит к самым первым микропроцессорам. Основная архитектура команд обеспечивает выполнение программ, написанных для процессоров 8086 и 8088 (которые имеют одну и ту же архитектуру команд), и отчасти даже для 8080 — 8-разрядного процессора, который был популярен в 70-е годы. На 8080, в свою очередь, в значительной степени повлияли требования совместимости с процессором 8008, построенным на базе процессора 4004 (4-раз- рядной микросхемы, применявшейся еще в каменном веке).
С точки зрения программного обеспечения компьютеры 8086 и 8088 были 16-разрядными (хотя компьютер 8088 содержал 8-разрядную шину данных). Их последователь, 80286, также был 16-разрядным. Его главным преимуществом был больший объем адресного пространства, хотя очень немногие программы его использовали, поскольку оно состояло из 16 384 64-килобайтных сегментов, а не представляло собой линейную 230-байтную память.
Процессор 80386 был первой 32-разрядной машиной, выпущенной компанией Intel. Все последующие процессоры (80486, Pentium, Pentium Pro, Pentium II, Pentium III, Pentium 4, Celeron, Xeon, Pentium M, Centrino и т. д.) имеют точно такую же 32-разрядную архитектуру, которая называется IA-32, поэтому мы сосредоточим наше внимание именно на этой архитектуре. Единственным существенным изменением архитектуры со времен процессора 80386 было введение в более поздние версии Pentium MMX-команд. Эти команды выполняют совершенно определенную функцию — повышают производительность мультимедийных приложений.
Pentium 4 имеет 3 операционных режима, в двух из которых он работает как 8086. В реальном режиме все функции, которыми был наделен процессор со времен 8088, отключаются, и Pentium 4 работает как простой процессор 8088. При программной ошибке происходит полный отказ системы. Если бы компания Intel занималась разработкой человеческих существ, то внутрь каждого такого существа непременно помещался бы специальный бит, возвращающий человека в режим функционирования своих предков (примитивный мозг, отсутствие речи, обитание на деревьях, сугубо банановая диета и т. д.).
На следующей ступени находится режим виртуального процессора 8086, который делает возможным исполнение старых программ, написанных для 8088, но с защитой. Чтобы запустить старую программу 8088, операционная система создает специальную изолированную среду, которая работает как процессор 8088, если не считать того, что при программном сбое операционной системе передается соответствующая информация, и полного краха системы не происходит. Когда пользователь Windows открывает окно MS-DOS, запускаемая в этом окне программа выполняется в режиме виртуального процессора 8086 — это позволяет защитить Windows от возможных вольностей DOS-программ.
Последний режим — это защищенный режим, в котором Pentium 4 работает как Pentium 4, а не как 8088. В этом режиме доступны 4 уровня привилегий, задаваемые битами во флаговом регистре (PSW). Уровень 0 соответствует привилегированному режиму на других компьютерах и обеспечивает полный доступ к машине. Этот уровень используется операционной системой. Уровень 3 предназначен для пользовательских программ. На этом уровне блокируется доступ к определенным командам и регистрам управления, чтобы сбой какой-нибудь пользовательской программы не привел к краху всей системы. Уровни 1 и 2 применяются редко.
Pentium 4 имеет огромное адресное пространство. Память разделена на 16 384 сегмента, каждый из которых занимает адреса от 0 до 232 - 1. Однако большинство операционных систем (включая UNIX и все версии Windows) поддерживают только один сегмент, поэтому для прикладных программ обычно доступно линейное адресное пространство размером 232 байт, причем иногда часть этого пространства занимает сама операционная система. Каждый байт в адресном пространстве имеет свой адрес. Слова состоят из 32 бит. Байты нумеруются справа налево (то есть самый первый адрес соответствует самому младшему байту).
Регистры процессора Pentium 4 показаны на рис. 5.3. Первые четыре регистра, ЕАХ, ЕВХ, ЕСХ и EDX, — 32-разрядные. Это регистры общего назначения, хотя у каждого из них есть определенные особенности. ЕАХ — основной арифметический регистр; ЕВХ предназначен для хранения указателей (адресов памяти); ЕСХ связан с организацией циклов; EDX нужен для умножения и деления — этот регистр вместе с ЕАХ содержит 64-разрядные произведения и делимые.
Младшие 16 и 8 бит в каждом из рассматриваемых регистров — это самостоятельные 16- и 8-разрядный регистры соответственно, позволяющие легко манипулировать 16- и 8-разрядными значениями. В компьютерах 8088 и 80286 имеются только 8- и 16-разрядные регистры, 32-разрядные регистры появились в системе 80386 вместе с приставкой Е (Extended — расширенный).
Следующие три регистра также являются регистрами общего назначения, но с большей степенью специализации. Регистры ESI и EDI предназначены для хранения указателей и в основном ориентированы на аппаратную поддержку строковых команд: ESI указывает на исходную строку, EDI — на целевую. Регистр EBP тоже предназначен для хранения указателей и обычно используется для указания на базу текущего фрейма локальных переменных, как и регистр LV в машине IJVM. Такой регистр обычно называют указателем фрейма. Наконец, регистр ESP — это указатель стека.
Следующая группа регистров от CS до GS — сегментные регистры. Это электронные трилобиты — атавизмы, оставшиеся от процессора 8088, которому через 16-разрядные адреса было доступно 220 байт памяти. Достаточно сказать, что когда Pentium 4 работает в режиме использования единого линейного 32-разрядно- го адресного пространства, их можно смело игнорировать. Регистр EIP (Extended Instruction Pointer — расширенный указатель команд) представляет собой счетчик команд. Регистр EFLAGS — флаговый.
Общий обзор уровня архитектуры набора команд UltraSPARC III
Архитектура SPARC была впервые введена в 1987 году компанией Sun Microsystems. Эта архитектура стала одной из первых RISC-архитектур промышленного назначения. Она была основана на исследовании, проведенном в Беркли в 80-е годы [161, 164]. Изначально архитектура SPARC была 32-разрядной, но UltraSPARC III — это 64-разрядная машина, основанная на архитектуре Version 9 SPARC, и именно ее мы будем описывать в этой главе. В целях согласованности с остальными частями книги мы будем называть данную систему UltraSPARC III, хотя на уровне архитектуры набора команд все машины UltraSPARC идентичны.
Структура памяти машины UltraSPARC III очень проста — линейный массив размером 264 байт. В настоящее время реализовать ее невозможно, поскольку память слишком велика (18 446 744 073 709 551 616 байт). Современные реализации имеют ограничение на размер адресного пространства, к которому они могут обращаться (244 байт у UltraSPARC III), но в будущем это число увеличится. Байты нумеруются слева направо, но можно перейти на нумерацию справа налево, установив один из битов во флаговом регистре.
Важно, что предельное число адресуемых байтов больше, чем требуется для реализации архитектуры команд, поскольку в будущем скорее всего понадобится увеличить объем памяти, к которой может обращаться процессор. Одна из самых серьезных проблем состоит в том, что архитектура команд ограничивает размер адресуемой памяти. Это — проявление глобальной информационной проблемы (имеющихся битов всегда не хватает), которая, вероятно, не разрешится никогда. Когда-нибудь наши внуки будут удивляться, как же могли работать компьютеры, содержащие всего 32-разрядные адреса и 4 Гбайт памяти.
Архитектура команд SPARC достаточно проста, хотя организация регистров была немного усложнена, чтобы сделать вызовы процедур более эффективными. Практика показывает, что организация регистров требует больших усилий, и, хотя обычно эти усилия того не стоят, правило совместимости не позволяет от этого отказаться.
В системе UltraSPARC III имеется две группы регистров: 32 64-разрядных регистра общего назначения и 32 регистра для команд с плавающей точкой. Регистры общего назначения называются R0-R31, но в определенных контекстах используются другие названия. Варианты названий регистров и их функции приведены в табл. 5.1.
Таблица 5.1. Регистры общего назначения в системе UltraSPARC III
Регистр |
Другой вариант названия |
Назначение |
R0 |
GO |
Аппаратный нуль. То, что сохранено в этом регистре, просто игнорируется |
R1-R7 |
G1-G7 |
Содержат глобальные переменные |
R8-R13 |
00-05 |
Содержат параметры вызываемой процедуры |
R14 |
SP |
Указатель стека |
R15 |
07 |
Временный регистр |
R16-R23 |
L0-L7 |
Содержат локальные переменные для текущей процедуры |
R24-R29 |
I0-I5 |
Содержат входные параметры |
R30 |
FP |
Указатель на базу текущего стекового фрейма |
R31 |
17 |
Содержит адрес возврата для текущей процедуры |
Все регистры общего назначения 64-разрядные. Все они, кроме R0, который всегда нулевой, могут считываться и записываться при помощи различных команд загрузки и сохранения. Назначение этих регистров, приведенное в табл. 5.1, отчасти определено по соглашению, отчасти зависит от используемого аппаратного обеспечения. Однако, вообще говоря, не стоит отклоняться от указанного назначения, если вы не являетесь крупным специалистом по компьютерам SPARC. Программист должен быть уверен, что программа правильно обращается к регистрам и выполняет с ними допустимые арифметические действия. Например, очень легко загрузить числа с плавающей точкой в регистры общего назначения, а затем произвести над ними целочисленное сложение — операцию, результатом которой будет полнейшая чепуха, но которую центральный процессор обязательно выполнит, если того потребует программа.