Глава 1. Системное программное обеспечение и архитектура ЭВМ

advertisement
Глава 1. Системное программное
обеспечение и архитектура ЭВМ
Системное программное обеспечение, будь то операционная система, ассемблер или
компилятор для какого-нибудь языка высокого уровня, всегда создается для
конкретной вычислительной машины. Машинная зависимость является одной из
характерных особенностей, которая во многом отличает системное ПО от
прикладного. При разработке прикладной программы основное внимание
сосредоточено на предметной стороне дела, и особенности конкретного компьютера
мало влияют на программу — ЭВМ используется только как инструмент для
решения задачи. С другой стороны, операционная система обычно реализуется для
конкретной вычислительной системы, и на другом компьютере работать не будет. А
инструментальное программное обеспечение по сути своей предназначено для
создания программ на конкретную машину. В этом смысле системные программы
практически всегда зависят от компьютера, поэтому системный программист должен
прекрасно разбираться в устройстве вычислительных машин.
ЭВМ можно изучать под разным углом зрения. Например, можно рассматривать
компьютер как набор связанных функциональных блоков различного назначения. С
другой стороны, можно изучать множество логических элементов, и как они
используются при построении функциональных блоков. Инженеров-электронщиков,
очевидно, интересуют электрические схемы и прохождение сигналов. Такие
описания представляют собой различные модели компьютера, предназначенные для
изучения их человеком. Вместо слова «модель» часто употребляется термин
«спецификация» или «архитектура».
Программисты используют совсем другую модель. Модель компьютера для
программиста называется архитектурой команд [_] или просто архитектурой.
Программа является машинно-зависимой, если при ее разработке необходимо
учитывать особенности архитектуры.
Например, генератор кода в любом компиляторе является машинно-зависимой
частью.
Важнейшими особенностями архитектуры, которые оказывают наибольшее влияние
на разработку системного программного обеспечения, являются следующие:
 типы данных, которыми способен оперировать процессор;
 организация памяти и состав регистров процессора;
 набор команд и способы адресации аргументов.
При реализации операционной системы обязательно рассматривать еще организацию
системы прерываний и системы ввода-вывода.
С другой стороны, системное программное обеспечение имеет ряд аспектов, которые
совершенно не зависят от типа вычислительной системы. Например, архитектура
ЭВМ никак не влияет на синтаксический анализ программы, выполняемый любым
компилятором. На работе мейкера тоже никак не отражаются ни организация памяти,
ни набор регистров. В трансляторе с ассемблера не зависит от архитектуры команд
«сбор» меток в таблицу имен. Даже в операционной системе есть машиннонезависимые компоненты, например, командный процессор.
При изучении системного программного обеспечения желательно четко отделить
машинно-зависимую часть от остальных. Такое деление позволит сосредоточиться на
действительно важных характеристиках системных программ. Помимо всего прочего
явное отделение машинно-зависимой части существенно облегчает перенос ПО на
другую архитектуру.
Реальные и виртуальные компьютеры
В настоящее время различают несколько различных типов архитектур [_]:
 архитектура с полным набором команд (Complex Instruction Set Computer, CISC);
 архитектура с сокращенным набором команд (Reduced Instruction Set Computer,
RISC);
 архитектура с безоперандным набором команд (Removed Operand Set Computer,
ROSC).
В наши дни наиболее распространена архитектура CISC. Характерными чертами
такой архитектуры являются:
 небольшое число регистров общего назначения;
 большое количество машинных команд, некоторые из которых весьма сложны;
 разнообразие способов адресации операндов;
 множество форматов команд различной длины.
Типичным представителем компьютеров с такой архитектурой является Pentium [_].
Архитектура с сокращенным набором команд обладает прямо противоположными
характеристиками:
 большое количество регистров общего назначения;
 относительно небольшое множество простых машинных команд;
 количество способов адресации ограничено; фактически все команды, кроме
команд загрузки и сохранения, выполняют операции на регистрах;
 команды имеют одинаковую длину, и количество форматов команд ограничено.
Все это позволило существенно упростить аппаратуру и значительно повысить
быстродействие. Как правило, такая архитектура используется в суперЭВМ вроде
Cray. Однако и микропроцессоры «доросли» до RISC.
Архитектура с безоперандным набором команд основана на применении стека.
Появление ROSC-архитектуры вызвано стремлением сократить семантический
разрыв [_], существующий между языками высокого уровня и аппаратурой
компьютера. Стеки широко используются в компиляторах для реализации языков
высокого уровня. И в оттранслированной программе стеки используется тоже очень
широко. Например, вложенные или рекурсивные вызовы подпрограмм, реализация
локальной области видимости, вычисление арифметических выражений проще всего
выполнить с помощью стека.
В настоящее время можно наблюдать тенденцию сближения архитектур. Например, в
микропроцессоре Pentium мы можем обнаружить элементы и RISC-архитектуры, и
ROSC-архитектуры [_]. Однако большинство реальных ЭВМ по тем или иным
причинам часто обладают нестандартными или даже уникальными особенностями. В
том же Pentium многочисленные методы адресации разрешается задавать только для
одного из операндов, а второй непременно должен быть в регистре. Кроме того,
регистры (базовый и индексный), используемые для формирования адреса операнда,
задаются неявно с помощью комбинации 5 битов в специальном байте modR/M в
составе команды. Мало того, в формировании физического адреса помимо
аргументов в команде неявно участвует один из сегментных регистров. Обычно он
выбирается процессором по умолчанию в зависимости от команды и метода
адресации, однако его можно назначить принудительно с помощью префикса замены
сегмента.
Такие особенности фактически не имеют отношения к принципам организации
системного ПО, но сильно усложняют машинно-зависимую часть, например
формирование двоичных команд в трансляторе с ассемблера. Поэтому мы будем
использовать абстрактную учебную машину VM (Virtual Machine). Это позволит нам
четко отделить теоретические аспекты и основные концепции от деталей реализации,
связанные с архитектурой команд.
П р им еч а н ие
В настоящее время абстрактную машину обычно называют виртуальной. Иногда
используется термин «псевдомашина». Мы будем использовать эти термины как
эквивалентные.
Эта книга не первая, в которой для изучения программирования используется
абстрактная учебная машина. Например, Л.Бек в книге [_] по системному
программированию использовал две разных учебных машины. Один из «гуру»
программирования Никлаус Вирт в книге [_] при реализации компилятора для
учебного языка тоже описал и использовал виртуальный компьютер. В другой своей
книге о компиляторах [_] профессор Н.Вирт описывает виртуальный RISCкомпьютер, в коды которого выполняется трансляция языка Оберон-0. В книге
Свердлова С.З. [_] при описании процесса трансляции с языка высокого уровня тоже
использована простая виртуальная машина. В книге [_] авторы учебного курса
«Введение в компьютерные системы», преподаваемого более чем в 90 университетах
мира, описывают архитектуру учебной виртуальной машины Y86. Однако самым
знаменитым абстрактным компьютером является учебная машина MIX, которую
описал Дональд Кнут в фундаментальном труде «Искусство программирования» [_].
Профессор Д.Кнут в настоящее время пишет четвертый и пятый том «Искусства
программирования» (книга уже издается по частям). В связи с тем, что современные
компьютеры довольно сильно отличаются от MIX, он разработал новую виртуальную
машину MMIX (Modification MIX). В книге [_], где описана архитектура этого
виртуального компьютера, профессор Д. Кнут написал:
MMIX — это RISC-компьютер, разработанный автором для иллюстрации
программирования на машинном уровне. В следующих изданиях «Искусства
программирования» MMIX заменит машину 60-х MIX.
Таким образом, при изучении системного программного обеспечения практически
всегда используется разработанная автором книги абстрактная машина, и мы не
будем отступать от этой традиции.
Виртуальные машины и промышленное
программирование
Виртуальные машины находят широкое применение не только в обучении, но и в
практике реального промышленного программирования. Одной из основных проблем
в системном программировании является проблема переносимости (мобильности)
программного обеспечения.
Программный продукт является переносимым (мобильным), если затраты на его
перенос с одной архитектуры на другую много меньше, чем его реализация на
новой архитектуре «с нуля».
Естественно, переносить имеет смысл только «многоразовое» ПО. Проблема
мобильности программного обеспечения изучалась еще в 60-е годы прошлого
столетия в основном на опыте переноса компиляторов. Именно в том время родилась
идея использования абстрактной машины для облегчения переноса программ.
Рассмотрим суть идеи. Пусть требуется реализовать N языков программирования на
M различных архитектур. Очевидно, потребуется реализовать N*M компиляторов.
Пусть теперь определена подходящая виртуальная машина, и исходную программу
можно транслировать в коды виртуальной машины. Тогда требуется разработать
только N компиляторов, осуществляющих перевод исходной программы в коды
виртуальной машины, и для каждого из М целевых компьютеров потребуется
реализовать «исполнитель» команд виртуальной машины.
Такая схема имеет много преимуществ по сравнению с традиционной схемой
реализации компиляторов без виртуальной машины.
Во-первых, использование виртуальной машины в значительной степени повышает
переносимость: оттранслированная программа независима от исполняющей
платформы и будет работать на любой ЭВМ, где реализован соответствующий
исполнитель. С другой стороны, и компилятор, реализующий перевод исходной
программы в коды виртуальной машины, становится менее зависимым от целевой
платформы.
Во-вторых, налицо существенное сокращение объема работ: вместо N*M программ
требуется только N+M программ. Поскольку виртуальную машину можно сделать
настолько удобной, насколько возможно, — это обычно позволяет существенно
уменьшить сложность реализации компилятора. Реализовать же исполнитель можно
самыми разными способами:
 можно реализовать прямой интерпретатор, который и будет выполнять
программы в кодах виртуальной машины на исполняющей машине;
 можно реализовать транслятор из кодов виртуальной машины в коды целевой
машины (генератор кода);
 еще один способ – реализовать транслятор с ассемблера виртуальной машины в
ассемблер исполняющего компьютера.
Преимуществом первого подхода является то, что интерпретатор можно сделать
переносимым (например, реализовать его на стандартном С++). Другим важным
преимуществом интерпретатора является то, что имеется возможность реализовать
разные режимы работы, например, пошаговую трассировку команд с выводом
результатов выполнения каждой команды в файл.
Обычно недостатком первого подхода называют низкую эффективность. Второй и
подход обеспечивают выполнение программ со скоростью процессора целевой
машины. Однако в этом случае мы лишаемся преимущества переносимости,
поскольку транслятор в коды целевой машины очевидно машинно-зависим.
При сочетании первого и второго подхода получаем еще один вариант реализации
исполнителя: JIT-компилятор.
Для реализации третьего подхода требуется разработка ассемблера виртуальной
машины. Сам же транслятор можно реализовать несколькими способами: можно
реализовать прямой транслятор; можно использовать макроассемблер целевой
машины, определив для каждого оператора ассемблера виртуальной машины
соответствующий макрос.
Таким образом, использование виртуальной машины обеспечивает беспрецедентную
гибкость при разработке системного ПО.
Помимо этого абстрактная машина обеспечивает еще одно важнейшее свойство
программ: совместимость. Поскольку все компиляторы транслируют в коды одной
машины (виртуальной), появляется возможность разрабатывать отдельные
компоненты большой системы на разных языках программирования. Компоновщик
без особых проблем соберет их в одну программу.
При наличии стольких достоинств вызывает недоумение отсутствие повсеместного
внедрения виртуальных машин в практику промышленного программирования. Тому
есть несколько причин. Во-первых, до недавнего времени считалось, что применение
абстрактных машин существенно снижает эффективность выполнения программ. В
прежние времена это было справедливо, но в настоящее время производительность
вычислительных систем настолько высока, что тезис о неэффективности в
значительной мере утратил свою актуальность (тем не менее, существуют сферы
применения ЭВМ, в которых эффективность важнее всех остальных характеристик).
Во-вторых, не всякая программа может быть реализована как программа для
виртуальной машины. Например, операционные системы по своей природе должны
работать на реальной машине.
Промышленные виртуальные машины
В качестве одного из первых примеров абстрактной машины можно назвать OCODE,
которая была разработана для реализации мобильного компилятора языка BCPL [_].
Самой известной абстрактной машиной была P-машина1, которая была разработана
при реализации первых промышленных компиляторов языка Pascal и использовалась
в системе UCSD-Pascal [_]. Pascal-программы транслировались в P-код (P-code) и
затем выполнялись программой-интерпретатором.
Нужно сказать, что все реализация всех языков программирования, разработанных
Н.Виртом и его командой, основывались на той или иной абстрактной машине.
Например, компилятор Modula-2 транслировал исходный код в M-код, являющийся
развитием P-кода. Для реализации Оберона ученик Вирта М.Франц [_] разработал
технологию генерации кода «на лету» (code generation on the fly), основанную на
специальном представлении программы в виде семантического словаря (Semantic
Dictionary Encoding, SDE). Компилятор Оберона транслировал исходную программу
в код SDE, который можно либо выполнять непосредственно, либо «на лету»
переводить в коды целевого компьютера. Одной из систем, которая использует SDE с
небольшими модификациями, является система BlackBox Component Builder [_],
разработанная фирмой Oberon Microsystems, которую основали Н.Вирт и его коллеги
и ученики.
Фирма Microsoft использовала варианты P-кода в ранних версиях системы Visual
Basic. Прямой наследницей P-машины является виртуальная машина Java (Java
Virtual Machine, JVM). Применение виртуальной машины в этом случае обеспечило
мобильность программ, реализованных на языке программирования Java.
Фирма Microsoft применила идею виртуальной машины при разработке своей
интегрированной среды Visual Studio.NET [_]. Хотя Microsoft предпочитает называть
эту виртуальную машину «общеязыковой исполняющей средой» (Common Language
Runtime, CLR), тем не менее, это настоящая виртуальная машина. В данном случае
эта абстрактная машина больше обеспечивает не переносимость программ, а их
совместимость в рамках одной среды. Отдельные компоненты большой программы
можно разрабатывать на разных языках, входящих в состав Visual Studio.NET, а
затем собрать в единую систему.
Еще одной интересной разработкой, в основе которой лежит абстрактная машина,
является системы Forth [_].
Интересно, что все промышленные виртуальные машины имеют стековую ROSCархитектуру. Это связано с тем, что такая архитектура наиболее приспособлена для
языков высокого уровня. Рассмотрим более подробно некоторые «промышленные»
виртуальные машины. Начнем с абстрактной P-машины, поскольку она послужила
прототипом и для JVM, и для CLR.
1
Английская буква «P» в этом сочетании произошла от слова «pseudo».
P-машина
В системе UCSD-Pascal результатом трансляции2 исходной Pascal-программы
является программа на P-коде, которая состоит из двух компонент: массив кодов и
структура, представляющая модель памяти (рис. 1.1) для данной Р-программы.
Рис.1.1. Структура памяти Р-программы
Модель памяти содержит статическую часть, где записаны константы, и
динамическую часть, где размещаются стек и куча. Структура динамической памяти
представлена на рис. 1.2. Для работы со стеком и кучей процессор P-машины
содержит несколько регистров, в которых записаны адреса динамической памяти:
 PC — счетчик адреса команд;
 SP — указатель стека процедуры;
 MP — указатель начала стекового фрейма;
 NP — указатель кучи;
 EP — указатель границы общего стека в динамической памяти.
Подробное описание компилятора, ассемблера и интерпретатора можно найти в
книге «Pascal Implementation», написанной Стивеном Пембертоном (Steven
Pemberton) и Мартином Даниэлсом (Martin Daniels). Книга размещена в Интернете по
адресу __________________
2
Рис. 1.2. Структура динамической памяти Р-машины
Для каждого вызова процедуры (в том числе и рекурсивного) в общем стеке
выделяется стековый фрейм, структура которого показана на рис. 1.3. В этом фрейме,
начало которого отмечает указатель MP, выделяется место для локального стека,
вершину которого определяет указатель SP. Другой конец фрейма записан в регистре
EP. Указатель MP отмечает начало служебной части фиксированного размера, с
помощью которой обеспечиваются связи с вызывающей процедурой:
 static link обеспечивает доступ данной процедуры к переменным объемлющей
процедуры3;
 dynamic link — это связь с фреймом стека вызывающей процедуры; содержит
значение MP вызывающей процедуры;
 previous EP хранит значение EP вызывающей процедуры;
Здесь же, в служебной части размещается адрес возврата и выделяется место для
возвращаемого значения функции. После фиксированной части помещаются
параметры процедуры (если есть), локальные переменные (если есть), и выделяется
место для локального стека.
Напомним, что в языке Pascal процедуры могут быть вложенными, и вложенная
процедура имеет доступ к переменным объемлющей процедуры.
3
Рис.1.3. Фрейм процедуры в общем стеке.
В листинге 1.1 показана простая программа на языке Pascal, состав стека P-машины
для которой можно видеть на рис. 1.4.
Листинг 1.1. Программа на языке Pascal
{ главная программа }
program main (input, output);
var j: integer;
{ вложенная процедура P }
procedure P;
var i: integer;
{ вложенная процедура Q }
procedure Q;
begin
if i > 0 then begin
i := i - 1; j := j + 1;
Q { рекурсивный вызов процедуры Q }
end
else write(input, 0);
end;
{ тело процедуры P }
begin
i := 2;
Q; { вызов процедуры Q }
end;
{ тело главной программы }
begin
j := 0;
P; { вызов процедуры P }
end.
Рис. 1.4. Стек программы из листинга 1.1.
На рисунке можно видеть, что процедура Q вызвана рекурсивно 3 раза, и для каждого
вызова создан фрейм стека, который связан динамическими связями с предыдущим
вызовом. Статические связи показывают текстуальную вложенность процедур.
Р-машина оперирует данными различных типов: целыми и вещественными числами,
булевскими величинами, множествами, символами, адресами. Фактически типы
данных P-машины совпадают с типами данных языка Pascal с учетом конкретной
платформы. Р-машина является стековой машиной. В систему команд входят всего
60 операций, большинство из которых являются стековыми: операнды извлекаются
из стека и результат помещается в стек. Команды имеют следующие форматы:
 КОП P Q
 КОП P
 КОП Q
 КОП
КОП — это код операции. Аргумент P используется для определения уровня
статического блока, а Q, как правило, определяет смещение во фрейме. Иногда Q
представляет собой непосредственный операнд-константу.
Команды без параметров обычно применяются к одному или двум элементам стека,
и результат помещают снова в стек. В качестве примеров можно привести
следующие операции:
 ADR — сложение вещественных; извлекает два верхних элемента стека, которые
являются вещественными числами, выполняет сложение и помещает результат в
стек;
 INN — проверка принадлежности множеству; извлекает из стека два верхних
элемента, которые представляют собой множество и элемент; выполняется
проверка принадлежности элемента множеству и на вершину стека помещается
булевское значение;
 FLT — преобразовать целое на вершине стека в вещественное;
Команды загрузки-сохранения и пересылки содержат, как правило, один или два
аргумента, например:
 LDCI 4 — загрузка целой константы 4 в стек;
 LODI 0 5 — загружает в стек целое значение из слов с адресом (0,5);
 LDA 0 6 — загрузка в стек адреса (0,6);
 STRI 1 5 — сохранить значение с вершины стека в слове с адресом (1,5);
 MOV 10 — извлечь из стека исходный и целевой адрес и переслать 10 слов,
начиная с исходного адреса по целевому адресу;
В состав системы команд входит несколько команд перехода, например:
 UJP L2 — безусловный переход по метке L2;
 FJP L5 — условный переход по метке L5, если на вершине стека false.
Метки располагаются непосредственно в кодовой части среди команд. Эти команды
используются для организации циклов в Р-программе. Например, оператор цикла
while (выражение) оператор
представляется следующей схемой в последовательности команд в массиве кодов:
L1
код
FJP
код
UJP
L2
для вычисления выражения и помещения значения на вершину стека
L2
для выполнения оператора
L1
В систему команд входят команды вызова стандартных и пользовательских
процедур. Требуемые параметры, естественно, должны быть предварительно
помещены в стек. Например, команда CSP COS означает, что требуется вызвать
стандартную функцию косинуса, которая извлекает аргумент с вершины стека и
результат помещает туда же.
Система команд P-машины включает 4 команды для организации вызова
пользовательских процедур: MST, CUP, ENT и RET. Каждая пользовательская
процедура начинается с двух команд ENT, которые устанавливают EP и SP для
данного вызова процедуры. Возврат из процедуры осуществляется командой RET,
которая освобождает стек. Вызов пользовательской процедуры осуществляется по
следующей схеме:
MST P
код для помещения параметров в стек
CUP P Q
Операнд P определяет уровень вложенности вызова процедуры. Команды MST и CUP
формируют фиксированную часть фрейма стека, устанавливая MP, размещая там
связи и адрес возврата.
Программу на P-коде можно в дальнейшем откомпилировать в машинный код
конкретной машины, однако это обычно не делают. Программа на Р-коде выполняет
интерпретатор. В конце 70-х годов использование Р-машины способствовало
широкому распространению языка Pascal. При наличии компилятора, написанного на
Pascal, для его использования в новой среде требовалось реализовать только
интерпретатор P-кода для этой среды, на что уходило около месяца работы.
Java Virtual Machine
Виртуальная Java-машина4 (Java Virtual Machine, JVM) является значительно более
сложной по сравнению с P-машиной. Основными целями при ее разработке были
эффективность, защищенность и переносимость. Программы представляются для
JVM в байт-коде и являются результатом трансляции программ на языке Java [_],
разработанном в фирме Sun Microsystems Inc. Программа на Java представляет собой
множество классов. Результатом трансляции каждого исходного класса является
отдельный класс-файл. Основными компонентами JVM являются:
 Загрузчик классов, который загружает, связывает и инициализирует классы;
 Исполнитель (интерпретатор), который и выполняет программу в байт-кодах;
 Интерфейс потоков, управляющий параллельной работой;
 Модуль управления памятью, который управляет кучей, где хранятся объекты и
массивы;
 Модуль управления обработкой исключительных ситуаций, систематически
отслеживающий возникающие в процессе выполнения исключительные ситуации
и ошибки;
Подробное описание (Java Virtual Machine Specification) можно найти на сайте
фирмы Sun Microsystems Inc. по адресу java.sun.com.
4
 Модуль управления защитой, который препятствует запуску «враждебных»
программ.
Каждый класс-файл содержит байт-код всех описанных методов, таблицу имен,
таблица связей с суперклассами и т.д. Вся эта информация записана в единственной
структуре5 ClassFile следующего вида:
ClassFile
{
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
};
Типы u2 и u4 — это беззнаковые целые размером 2 и 4 байта соответственно.
Остальные типы определены в составе Java-машины. Например, тип field_info
представляется структурой со следующими полями:
field_info
{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
};
Названия полей всех структур говорят сами за себя. Например, поле access_flags в
структуре ClassFile определяет, как был объявлен данный класс: public, final,
abstract или interface. В структуре field_info поле с таким же названием,
очевидно, определяет модификатор доступа к данному полю: public, protected,
private, static, final. В структуре ClassFile все поля с суффиксом _count
представляют собой размеры (количество элементов) массивов соответствующего
типа, представленных в этой же структуре.
Для представления элементов Java-машины используется С-подобный псевдокод,
описанный в Java Virtual Machine Specification.
Для эффективной работы JVM класс-файл представляется в двоичном формате.
Важнейшей особенностью реализации Java-машины является верификация классфайла перед выполнением. Верификатор контролирует корректность структуры
класс-файла, осуществляет проверку байт-кода. В частности, проверяется:
 наличие команд передачи управления, обращающихся к некорректным адресам;
 наличие методов, вызываемых с неправильными аргументами;
 некорректное управление стеком при опустошении и переполнении;
 корректность типов аргументов в командах.
Верификация обходится недешево, но позволяет избавиться от трудоемкой проверки
в процессе выполнения. Тем самым повышается скорость выполнения программы.
Типы данных Java-машины делятся на два вида: простые (primitive) и ссылочные
(reference). Аналогичное деление существует в языке Java, так что JVM практически
непосредственно отображает типы Java. Значения простых типов — это целые и
вещественные числа, которые определены в Java. Типа boolean, который определен в
языке Java, виртуальная Java-машина не поддерживает: значения этого типа
представляются как целые 0 (false) и 1 (true). Символьный тип char тоже
представляется целыми. В JVM используется один простой тип returnAddress,
который не определен в языке Java. Название «говорящее»: значением является адрес
возврата в вызывающую функцию. Значения простых типов размещаются в стеке.
К ссылочным типам относятся типы классов, типы массивов и типы интерфейсов.
Однако в спецификации сказано, что объект — это есть экземпляр класса или
массива, объектов типа интерфейса не существует. Объекты создаются в куче, а
доступ к ним осуществляется с помощью значений ссылочного типа, которые
являются указателями. Специальным значением ссылочного типа является null.
Виртуальная Java-машина поддерживает параллельные процессы, поэтому память
периода выполнения устроена значительно сложнее, чем в P-машине. Для каждого
потока (thread) JVM поддерживает регистр pc – счетчик адреса команды. Для
каждого потока организуется свой стек. При каждом вызове метода в стеке
выделяется фрейм, в котором содержатся локальные переменные и динамические
связи (подобные тем, которые изображены на рис. 1.3 и 1.4). А вот куча — одна
общая для всех потоков. Сама программа размещается в области методов (method
area). Собственно, это и есть кодовый сегмент, аналогичный кодовому сегменту Pмашины. С каждой областью методов связан пул констант.
Состав системы команд JVM тоже значительно шире, чем система команд P-машины
и включает более 160 команд, которые в спецификации JVM объединяются в
следующие группы:
 загрузка-сохранение;
 арифметические и битовые команды;
 преобразование типов;
 оперирование объектами и массивами;
 работа со стеком;
 передача управления;
 вызов методов;
 обработка исключительных ситуаций и параллельная работа.
Архитектура JVM стековая, поэтому большинство команд безоперандные, либо
имеют один-два операнда, как команды P-машины. Многие команды отличаются
только типами операндов. Например, в состав арифметических команд входит 4
команды сложения: iadd, ladd, fadd, dadd. Вообще, состав системы команд
производит впечатление несколько хаотичной разработки. Например, реализовано
семь команд для загрузки целых констант в стек, но только две — для загрузки
длинных (long) целых констант; три команды загрузки коротких вещественных (float)
и две — для загрузки длинных вещественных (double).
Поскольку основой JVM является P-машина, то и программа на байт-коде очень
похожа на программу в P-коде. Пусть в исходной Java-программе задана следующая
последовательность Java-операторов:
double i = 0.0;
while (i < 100.1)
i++;
Эти операторы компилируются в следующую последовательность байт-кодов:
0
1
2
5
6
7
8
9
10
13
14
dconst_0
dstore_1
goto 9
dload_1
dconst_1
dadd
dstore_1
dload_1
ldc2_w #4
dcmpg
iflt 5
Первые три команды реализуют инициализацию переменной i, следующие четыре
команды — операция инкремента переменной i. И наконец, последние четыре —
проверку выражения в условии оператора while. Числа слева — это смещение в
байтах соответствующей команды. Эти смещения задаются в командах перехода в
качестве операндов: в команде безусловного перехода goto и в команде условного
перехода iflt.
Тот же цикл с целой переменной i:
int i = 0;
while (i < 100)
i++;
Эти операторы транслируются в значительно более короткую последовательность
команд:
0
1
2
5
8
9
11
iconst_0
istore_1
goto 8
iinc 1 1
iload_1
bipush 100
if_icmplt 5
Инициализация переменной тоже выполняется тремя командами, а вот инкремент
осуществляется одной командой. Последние три команды — это проверка значения i
и условный переход.
Common Language Runtime
Разработка виртуальных машин
Разработка виртуальной машины состоит в разработке ее архитектуры. Для этого
требуется определить несколько моделей: модель памяти, модель регистров, модели
данных, модель процессора и алгоритм обработки команд, модели команд и алгоритм
выполнения каждой команды. Если требуется, то определяется модель стека.
Абстрактная машина AMF
В качестве первого примера определим простую абстрактную машину AMF (Abstract
Machine First).
Память AMF состоит из 512 ячеек-слов с адресами от 0 до 511. Одно слово памяти
содержит 32 бита. В каждом слове, кроме нулевого, может быть записано целое
число, вещественное число или команда. В нулевой ячейке всегда записан ноль: все
биты равны 0.
Целые числа являются знаковыми, отрицательные числа представляются в
дополнительном коде. Вещественные числа представляются как короткие
вещественные числа по стандарту IEEE-754 [_]. В соответствии с этим стандартом
вещественный ноль представляется точно так же, как целый: все биты слова равны 0.
Команды имеют следующий формат:
 код операции (5 разрядов);
 А1, А2, А3 – адреса операндов (каждый адрес по 9 разрядов).
Код операции представляет собой число от 0 до 31, а адресная часть составляет 27
бит. В соответствии с количеством адресов, машина AMF является 3-адресной6.
Состав операций представлен в табл. 1.1. В таблице определены только 21 команда
из 32. Это дает нам возможность добавить при необходимости несколько новых
6
Подобная структура команд была реализована в советских ЭВМ типа M-20.
команд. Для удобства
обозначением.
код каждой операции обозначается мнемоническим
Регистры процессора:
 RA – регистр адреса команды, в котором процессор хранит адрес выполняемой
команды; содержит 9 разрядов;
 RC – регистр команды, в котором процессор хранит выбранную из памяти и
выполняемую в данный момент команду;
 Flag – регистр признака результата, в который процессор записывает целое
число как результат некоторых операций: -1, если результат отрицательный, 0 –
если результат нулевой и +1, если результат положительный;
 Error – регистр ошибки: 0 – ошибки при выполнении операции отсутствуют, 1 –
при выполнении операции произошла ошибка;
Таблица 1.1
Код операции (КОП)
Комментарий
00
Stop – остановка работы процессора
01
Iadd – сложение целых чисел
02
Isub – вычитание целых чисел
03
Imul – умножение целых чисел
04
Idiv – деление целых чисел
05
Imod – остаток от деления целых чисел
08
Iin – ввод целых чисел
09
Iout – вывод целых чисел
10
ItoR – перевод целого в вещественное
11
Radd – сложение вещественных чисел
12
Rsub – вычитание вещественных чисел
13
Rmul – умножение вещественных чисел
14
Rdiv – деление вещественных чисел
16
Rcmp – сравнение вещественных чисел
18
Rin – ввод вещественных чисел
19
Rout – вывод вещественных чисел
20
RtoI – перевод вещественного в целое
25
Move – пересылка (копирование)
26
Xchg – обмен содержимым двух слов
30
Go – безусловный переход
31
Jmp – условный переход
Для описания алгоритма работы процессора и алгоритмов выполнения команд
введем некоторые формальные обозначения. Память представляет собой массив
mem[512]. Если х — это адрес, то mem[x] представляет собой содержимое ячейки
памяти с адресом x.
Процессор исполняет команды по следующему алгоритму:
1.
2.
RC<-mem[RA]; выборка очередной команды из памяти.
Выполнение операции, заданной в команде кодом операции; если код операции
ошибочный, то Error<-1.
3.
Если КОП=Stop или Error=1, то Конец; иначе RC<-(RC + 1).
4.
Перейти к 1.
Процессор начинает работу при «нажатии кнопки Пуск»7:
1.
Выполняется ввод массива слов с «устройства ввода», начиная с адреса 1; этот
массив слов представляет собой программу и заканчивается специальным
маркером конца массива;
2.
Error<-0; Flag<-0.
3.
RA<-1.
Далее процессор начинает автоматически выбирать команды из памяти.
Выполнение арифметических операций осуществляется по следующему алгоритму:
1.
R1<-mem[A1]; R2<-mem[A2]; Res<-(R1 op R2); mem[A3]<-Res.
2.
Если при выполнении операций были ошибки, то Error<-1.
3.
Если Error=0, то
Выбор Res: Res<0:Flag<-(-1); Res=0:Flag=0; Res>0:Flag<-(+1)
Здесь R1, R2 и Res представляю собой внутренние регистры процессора.
Команда сравнения выполняется аналогично команде вычитания, но результат
вычитания не записывается в память. Поэтому первый операнд нужно всегда
задавать нулевым. Таким образом, команда сравнения только устанавливает флаги.
Команда пересылки копирует содержимое по адресу A3 в слово по адресу A1:
R1<-mem[A3]; mem[A1]<-R1.
Команда обмена обменивает содержимое двух слов памяти:
R1<-mem[A1]; R2<-mem[A3]; mem[A1]<-R2; mem[A3]<-R2;
Под «кнопкой Пуск» в данном случае понимается начало работы интерпретатора
виртуальной машины (см. главу 2 «Реализация интерпретаторов»).
7
Команда безусловного перехода выполняет очень простое действие: RA<-A2. Тем
самым процессор выбирает следующую команду из слова с адресом A2.
Команда условного перехода изменяет адрес в регистре адреса в соответствии со
значением регистра Flag:
Выбор Flag: -1:RA<-A1; 0:RA<-A2; +1:RA<-A3;
Команды преобразования чисел работают подобно команде MOVE, но в процессе
пересылки выполняют преобразование числа из одного вида в другой. Команды
ввода осуществляют ввод массива чисел с внешнего устройства и запись его по
адресу A1; количество чисел задается непосредственно на месте A2. Команды вывода
выполняют вывод массива чисел на внешнее устройство. Адрес массива задается в
команде первым аргументом A1, а количество — на месте второго аргумента
непосредственно в команде, аналогично тому, как это определено в командах ввода.
Отметим, что в качестве массива целых чисел можно ввести массив команд.
Система команд машины AMF подобна системам команд ранних моделей реальных
компьютеров, разработанных в 50-е прошлого века. В частности, команда условного
перехода в точности соответствует условному оператору Фортрана в его ранних
версиях. Исключение составляют команды ввода-вывода и преобразования чисел: в
реальных компьютерах эти действия обычно выполнялись стандартными
подпрограммами. Но как уже было сказано, мы создаем виртуальную машину такой,
как нам удобно.
Абстрактная машина AMS
Для сравнения определим другую простую абстрактную машину AMS (Abstract
Machine Second).
Память AMS состоит из 1024 32-разрядных слов с адресами от 0 до 1023. Нулевое
слово содержит ноль. AMS обрабатывает такие же форматы данных, что и AMF.
Команда AMS имеет следующий формат:
 Код операции (6 разрядов);
 A1 – адрес операнда (10 разрядов).
Таким образом, длина команды составляет 16 бит, и в одном слове памяти
помещается две команды. AMS является одноадресным компьютером8. Разрядность
кода операции и позволяет определить до 64 различных операций (см. табл. 1.2). В
таблице показан практически тот же набор команд с небольшими изменениями,
обусловленными одноадресностью. Очевидно,
В связи с тем, что аргумент в команде единственный, в состав регистров процессора
входит регистр-сумматор RS, в котором может быть записано целое или дробное
число.
8
Аналогичное свойство было у советской ЭВМ БЭСМ-6.
За один раз процессор выбирает из памяти полное слово, в котором записано две
команды, поэтому состав регистров для обработки команд тоже немного отличается
от состава регистров процессора AMF:
 RM — регистр слова; в этот регистр процессор выбирает слово из памяти.
 RC — регистр команды, в который из регистра RM по очереди помещаются первая
и вторая команды.
Остальные регистры процессора — такие же, как в процессоре AMF.
Таблица 1.2
Код операции (КОП)
Комментарий
00
Stop – остановка работы процессора
01
Iadd – сложение целых чисел
02
Isub – вычитание целых чисел
03
Imul – умножение целых чисел
04
Idiv – деление целых чисел
05
Imod – остаток от деления целых чисел
06
Icmp – сравнение сумматора с целым числом
08
Iin – ввод целого числа
09
Iout – вывод целого числа
11
Radd – сложение вещественных чисел
12
Rsub – вычитание вещественных чисел
13
Rmul – умножение вещественных чисел
14
Rdiv – деление вещественных чисел
16
Rcmp – сравнение сумматора с вещественным числом
18
Rin – ввод вещественного числа
19
Rout – вывод вещественного числа
40
Load – загрузка сумматора
41
LdIR – загрузка сумматора с переводом целого в вещественное
42
LdRI – загрузка сумматора с переводом вещественного в целое
50
Store – сохранение сумматора
51
StIR – сохранение сумматора с переводом целого в вещественное
52
StRI – сохранение сумматора с переводом вещественного в целое
60
Go – безусловный переход
61
JZ – переход, если ноль
62
JG – переход, если больше
63
JL – переход, если меньше
Действия процессора AMS по «кнопке Пуск» отличаются от действий процессора
AMF обнулением регистра-сумматора, и установкой адреса первого выбираемого
слова в регистре RM.
Алгоритм выполнения команд отличается тем, что за одно обращение к памяти
процессор выбирает сразу две команды. Поэтому цикл обработки включает
выполнение двух команд: сначала левой (из старшей половины слова), потом правую
(из младшей половины слова):
1.
RM<-mem[RA]; выборка очередного слова с командами из памяти.
2.
RA<-RA + 1.
3.
RC<-high(RM); занести команду в регистр команд из старшей половины RM.
4.
Выполнение операции, заданной кодом операции; если КОП ошибочный, то
Error<-1.
5.
Если КОП=Stop или Error=1, то Конец;
6.
RC<-low(RM); занести команду в регистр команд из младшей половины RM.
7.
Выполнение операции, заданной кодом операции; если КОП ошибочный, то
Error<-1.
8.
Если КОП=Stop или Error=1, то Конец;
9.
Перейти к 1.
Выполнение арифметических операций осуществляется по следующему алгоритму:
1.
R1<-mem[A1]; RS<-(RS op R1).
2.
Если при выполнении операций были ошибки, то Error<-1.
3.
Если Error=0, то
Выбор Res: Res<0:Flag<-(-1); Res=0:Flag=0; Res>0:Flag<-(+1)
Здесь R1 является внутренним регистром процессора, а RS – регистр-сумматор.
Команды сравнения работают аналогично командам вычитания, но без записи
разности в сумматор. Таким образом, команды сравнения только устанавливают
регистр Flag.
Команда загрузки Load копирует содержимое по адресу A1 в регистр-сумматор:
RS<-mem[A1];
Команда сохранения Store выполняет обратное действие:
mem[A1]<-RS;
Команды загрузки с преобразованием чисел работают подобно команде Load, но в
процессе пересылки выполняют преобразование числа из одного вида в другой.
Аналогично команды сохранения с преобразованием перед записью значения
сумматора в память осуществляют преобразование числа к заданному виду.
Команда безусловного перехода практически во всех компьютерах работает
одинаково: RA<-A1. Однако в машине AMS передать управление можно только на
первую команду, размещенную в слове A1.
Команды условного перехода изменяют адрес в регистре адреса в соответствии со
значением регистра Flag. Однако адрес в команде единственный, поэтому каждая
команда проверяет только свой вариант значения регистра Flag.
Команда JZ:
Если Flag=0, то RA<-A1;
Команда JG:
Если Flag=+1, то RA<-A1;
Если условие не выполняется, то команда ничего не делает. Аналогично команде
безусловного перехода Go передача управления возможна только на первую команду,
размещенную в слове A1.
Команды ввода осуществляют ввод одного числа с внешнего устройства и запись его
по адресу A1. Команды вывода выполняют вывод одного числа на внешнее
устройство. Адрес числа задается аргументом A1.
Примеры программ
Пусть требуется ввести массив из 100 положительных вещественных чисел.
Перебирая числа массива, для каждого положительного числа вычислить обратную
величину и просуммировать, если она больше 1.
Сначала требуется распределить память: назначить конкретные адреса константам
и переменным. Массив разместим в ячейках с начальным адресом 100, сумму
поместим в ячейку с адресом 20. Обратная величина сохраняется в ячейке с адресом
21. Целую величина n = 100 (количество элементов массива) поместим после
команды Stop. Нам потребуются еще вещественная константа 1.0, и целая константа
1. Разместим их тоже после команды Stop.
Для сравнения возможностей AMF и AMS напишем одну и ту же программу для
обеих машин9. Программа для AMF показана в табл. 1.3, а программа для AMS — в
табл. 1.4. Вместо числовых кодов операций в таблицах записаны их мнемонические
обозначения. По возможности в обеих программах используются ячейки с одними и
теми же адресами.
Обратите внимание на то, что для переадресации элементов массива в обеих
программах приходится непосредственно модифицировать команды в памяти. Такая
практика — обычное дело при отсутствии регистров общего назначения.
Программы не являются оптимальными, и предназначены просто для демонстрации
возможностей виртуальных машин.
9
Таблица 1.3
Адрес Команда
001
2
3
4
5
6
7
8
9
010
1
2
3
4
5
6
7
8
Rin
Move
Rcmp
Jmp
Rdiv
Rcmp
sub
Jmp
Radd
Isub
Jmp
Iadd
Iadd
Go
Rout
Stop
1.0
00
00
Комментарий
100
020
000
005
021
000
009
020
018
014
003
005
000
020
000
100
000
000
009
016
021
009
020
018
014
017
017
003
001
000
000
000
100
009
100
016
008
021
017
011
003
005
000
000
000
000
000
000
000
001
100
Читать(x); массив x в ячейках 100 – 200
S=0
(0 - x[i]) < 0?; установка Flag
да, нет, нет; если x[i]<=0, то на переадресацию
Обратная = 1.0 / x[i]; установка Flag
(Обратная – 1.0) > 0 ?; установка Flag
нет, нет, да; Если разница < 0, то на переадресацию
S = S + Обратная; установка Flag
Переадресация-начало: n = n – 1; установка Flag
Если n <= 0, то Вывод
Модификация команды в слове 3
Модификация команды в слове 5
На 003 – обработка следующего x[i]
Вывод(S)
Стоп
Вещественная константа 1.0
Целая константа 1 – для переадресации х[i]
Переменная n с начальным значением 100
Во второй программе также отметим, что на месте второй команды приходится
иногда оставлять пустые полуслова из-за того, что команды перехода могут
передавать управление только на первую команду в слове. Обычно в составе системы
команд для таких случаев присутствует команда NOP – нет операции.
Таблица 1.4
Адрес
Команда
001 Load
Move
Store
n
2 Move
Rin
ve
Rcmp
3 JG
JZ
4 Load
Rdiv
5 Rcmp
Store
JG
6 Load
Isub
7 Store
0000
0020
2S3
0100
0100
0006
0006
0015
0100
0015
21
0012
00016
30017
2
0016
Комментарий
Сумматор = 0
Сумма = 0
Читать(x[i]); установка Flag
Сумматор - X[i] < 0;
Нет – переход на переадресацию
Нет – переход на переадресацию
Сумматор = 1.0
Сумматор (Обратная) = 1.0 / x[i]; установка Flag
Сумматор (Обратная) - 1.0 > 0?; установка Flag
ДА! Если Обратная > 1.0, то на суммирование
Сумматор = n
Сумматор = Сумматор – 1; установка Flag
n = Сумматор
JZ
8 Load
Iadd
9 Store
Load
0010 Iadd
Store
1 Load
Go
2 Radd
Store
3 Load
Go
4 Rout
Stop
5 1.0
0014
2
0002
6 00
00
7 00
00
8 00
00
0000
0100
0000
0001
0001
0001
0018
0002
0004
0017
0004
0000
0002
0020
0020
0000
0002
0020
0000
Если n =0, то на финиш
Сумматор = Команда из слова 2
Модификация обеих команд в слове
Запись команды в память
Сумматор = Команда из слова 3
Модификация только второй команды
Запись команды в память
Очищаем сумматор
На 0002 – обработка следующего x[i]
Сумматор (Обратная) = Сумматор + Сумма
Сумма = Сумматор
Очищаем сумматор
На 0002 – обработка следующего x[i]
Вывод(Сумма) - финиш
Вещественная константа 1.0 – полуслово 1
Вещественная константа 1.0 – полуслово 2
Целое n = 100 – полуслово 1
Целое n = 100 – полуслово 2
Целая константа 1 – полуслово 1
Целая константа 1 – полуслово 2
Константа переадресации x[i] (слово 0002)
Несмотря на то, что количество команд в программе для AMS значительно больше,
чем в программе для AMF, программы занимают в памяти одинаковое количество
слов.
А теперь разработаем более современную учебную виртуальную машину, которую и
будем использовать в дальнейшем
Учебная виртуальная машина VM
VM представляет собой типичный CISC-компьютер, включающей элементы RISC- и
ROSC-архитектур. В данной главе описаны базовые свойства учебной виртуальной
машины. В дальнейшем, по мере необходимости, будем добавлять в нее те или иные
особенности и новые «устройства».
Память и данные
Память VM состоит из 232 байтов с адресами от 0000 000016 до FFFF FFFF16.
П р им еч а н ие
Указан максимально возможный размер памяти VM. При реализации виртуальной
машины обычно задается меньший размер, например 64 килобайта.
Размер байта традиционно равен 8 битам. Размер адреса, таким образом, составляет 4
байта или 32 бита. Ячейки памяти имеют имена M[0], M[1], … M[232-1]; поэтому
если x – слово, то M[x] представляет байт памяти.
Байты могут объединяться в группы по 2, 4 и 8. Четыре байта составляют слово, два
байта называются полусловом, а восемь байт представляют собой двойное слово.
Таким образом, память VM состоит из 232 байт, или 231 полуслов, или 230 слов, или 229
двойных слов. Следовательно, если x — слово, запись M2[x], M4[x], M8[x] обозначают
полуслово, слово и двойное слово соответственно, которое содержат байт M[x]. Для
полноты картины мы также пишем, что M1[x] = M[x].
Интерпретация группы байтов зависит от используемой команды. Как практически
все современные компьютеры, VM способна обрабатывать данные следующих видов:
 беззнаковые двоичные целые числа;
 двоичные целые числа со знаком;
 дробные числа со знаком;
 битовые последовательности;
 последовательности байт и многобайтных величин.
Байты и многобайтные величины представляют либо знаковые, либо беззнаковые
двоичные целые числа. Младшая часть числа располагается в байте с меньшим
адресом. Такое расположение байтов числа получила название little-endian. Такой
порядок байтов используется, например, в микропроцессорах Intel.
П р им еч а н ие
Людям, читающим текст слева направо, такое расположение кажется не очень
удобным. Если мы представляем память как линейку байтов, пронумерованных слева
направо, то целые числа оказываются записанными «задом наперед»: сначала младшие
цифры, потом — старшие. Однако можно представить себе память, как вертикальный
столбик пронумерованных байтов, или даже как линейку байтов, пронумерованных
«по-арабски» справа налево. Тогда байты многобайтного целого числа будут
расположены в естественном порядке: младший байт по младшему адресу (см. рис.1.5).
Противоположный порядок, при котором по младшему адресу расположена старшая
часть числа, называется big-endian (см. рис. 1.5). Для человека такая запись кажется
естественной, однако при разработке процессора возникают некоторые сложность,
поскольку результат выполнения команды требуется записывать в память, начиная со
старшей части числа.
Рис.1.5. Представление целых чисел в памяти VM
Диапазоны беззнаковых целых чисел в VM следующие:
 байт
0 .. 255
 полуслово
0 .. 65535
 слово
0 .. 4 296 967 295
 двойное слово
0 .. 18 446 744 073 709 551 615
Таким образом, адреса памяти представляются как беззнаковые целые числа
размером в слово. Целые числа со знаком имеют следующие диапазоны:
 байт
-128 .. +127
 полуслово
-32 768 .. +32 767
 слово
-2 147 483 648 .. +2 147 483 647
 двойное слово
-9 223 372 036 854 775 808 .. +9 223 372 036 854 775 807
Полуслово обычно называют коротким целым, а двойное слово часто называется
длинным целым. Отрицательные целые числа представляются в дополнительном
коде. Например, -1 размером в один байт представляет собой двоичное число
111111112.
VM может оперировать дробными числами двух форматов:
 короткие — с одинарной точностью длиной 32 бита (4 байта);
 длинные — с двойной точностью длиной 64 бита (8 байт).
Машинное представление дробных чисел соответствует международному стандарту
IEEE-754 (см. Приложение 3). Интерпретация слова и двойного слова как чисел с
плавающей запятой зависит от используемой команды.
Для обозначения типов данных VM будем пользоваться английскими терминами,
большинство из которых можно обнаружить в С++, в Java или в C#10. Числа с
плавающей запятой называются так:
 float — короткое дробное число (4 байта);
 double — длинное дробное число (8 байт);
Целые знаковые числа обозначаются таким образом:
 byte — байт;
 short — полуслово (2 байта);
 int — слово (4 байта)
 long — двойное слово (8 байт).
Для обозначения беззнаковых целых добавляется слово unsigned:
 unsigned byte — байт;
 unsigned short — полуслово (2 байта);
 unsigned int — слово (4 байта)
 unsigned long — двойное слово (8 байт).
Типы данных VM, хотя и называются так же, не всегда совпадают с типами
указанных языков программирования.
10
Регистры
Регистры в любом «железном» компьютере делятся на два вида: регистры общего
назначения и системные регистры.
Регистры общего назначения
Виртуальная машина имеет 64 регистра общего назначения размером в слово. Эти же
регистры могут использоваться как 32 регистра размером в двойное слово. Операции
с целыми и дробными числами выполняются только регистрах. Однако команды
загрузки и пересылки «рассматривают» регистры как локальную память процессора
из 256 байт, и «умеют» загружать и сохранять любой байт, полуслово, слово или
двойное слово. Далее мы будем использовать символические имена:
 регистры-байты B0..B255;
 регистры-полуслова S0..S127;
 регистры-слова W0..W63;
 регистры-двойные слова D0..D31;
Эти обозначения используются в программе на ассемблере, а в команде,
находящейся в памяти VM, стоит номер регистра, занимающий один байт. Номера
регистров многобайтных величин тоже занимают в команде один байт:
 номер регистра-полуслова четный (последний бит номера равен 0);
 номер регистра-слова кратен 4 (два последних бита номера равны 00);
 номер регистра-двойного слова кратен 8 (три последних бита номера равны 000).
В любом четырехбайтном регистре может находиться либо целое число (знаковое
или беззнаковое), либо короткое дробное число; в любом восьмибайтном — либо
длинное дробное число, либо длинное целое (знаковое или беззнаковое).
Интерпретация регистров зависит от используемой команды.
Четырехбайтные регистры могут интерпретироваться в ряде команд как адресные
регистры A0..A63. В этом случае содержимое регистра интерпретируется как адрес.
Так как адрес фактически является целым беззнаковым числом, то с адресами можно
выполнять любые операции, допустимые для целых беззнаковых чисел.
Системные и управляющие регистры
PC (Program Counter) — регистр адреса команды, 32 бита. В регистре всегда хранится
адрес текущей команды. Так как длина команды кратна 2, то его значение всегда
четное.
SP (Stack Pointer) — регистр указателя стека. В регистре хранится адрес вершины
стека. В стеке размещаются только слова (или адреса). При помещении величины в
стек сначала адрес в регистре уменьшается на 4, а затем величина помещается в стек;
при извлечении из стека сначала извлекается величина, а потом адрес в регистре
увеличивается на 4. Таким образом, указатель стека всегда показывает на слово на
вершине стека.
PSW (Processor Status Word) — слово состояния процессора, 32 бита; включает
аварийные признаки результата, маску разрешения прерываний по аварийным
признакам, и набор управляющих флагов, устанавливающих специальные режимы
работы процессора. Одним из флагов результата является флаг переноса CF.
Трассировка, прерывания и регистры отладки будут описаны в главе об отладчиках.
Система команд
Систему команд можно разделить на 2 подмножества: 254 команды составляют
основной набор операций. Эти команды имеют код операций, состоящий из одного
байта в диапазоне от 0016 до FE16. Код FF16 определяют еще один набор из 256
операций, у которых код операции занимает 2 байта. Таким образом, если первые
семь бит кода операции не равны 11111112, то код операции занимает один байт, и
команда относится к основному набору операций. К основному набору относится и
команда с кодом 111111102 = 25410. Если же все восемь бит операции равны
111111112, то команда относится к расширенному множеству операций, и код
операции состоит из 2 байтов.
П р им еч а н ие
Полная таблица кодов операций VM приведена в Приложении 1.
В этой главе опишем основной набор операций. По мере необходимости будем
описывать расширенное множество операций. Основной набор включает следующие
типичные группы операций:
 переходы;
 загрузка-сохранение;
 целая арифметика;
 арифметика с дробными числами;
 битовые и сдвиги;
 стековые;
 ввод-вывод.
Длина команды может быть 2, 4, 6 и 8 байтов. Основной формат трехадресный и 4байтный, однако есть много одноадресных команд и ряд двухадресных.
Методы адресации операндов
Аргументы располагаются в команде, в регистре, в памяти и стеке. Методы
адресации аргументов следующие:
 непосредственная; непосредственный целый операнд располагается в команде и
имеет размер 1, 2 или 4 байта; обозначается как iB (байт), iS (полуслово), iW
(слово);
 абсолютная; абсолютный адрес размером 4 байта задается в команде и
обозначается как A;
 относительная; размер смещения 2 или 3 байта; обозначается как RA;
 регистровая; обозначается конкретным типом регистра $B, $S, $W, $D;
 регистровая косвенная; обозначается как адресный регистр $A;
 регистровая базово-индексная: второй и третий аргумент — регистры-слова.
В стековых операциях выполняется автоинкремент или автодекремент указателя
стека SP.
Основной набор операций
Целое число со знаком, находящееся в регистре общего назначения, будем
обозначать s($R), беззнаковое целое — как u($R). Короткое дробное обозначим как
f($W), а длинное — d($D). При описании команд будем по традиции использовать
символический код операции — английское слово или англоязычную аббревиатуру.
Символический код операции соответствует мнемонике языка ассемблера VM.
Переходы
Самой важной группой команд являются команды перехода, поэтому начнем с них.
Эта группа обычно включает следующие виды команд:
 команды условного перехода;
 команды безусловного перехода;
 команды перехода к подпрограмме и возврата из подпрограммы;
 команды цикла.
Еще в эту же группу обычно включают команды прерываний (и возврата из
прерываний), но мы рассмотрим их в главе об отладчиках.
Группа команд условного перехода включает 8 команд. Команды условного перехода
являются двухадресными и выполняют относительный переход по смещению RA в
зависимости от содержимого регистра $W. При невыполнении условий перехода
выполняется следующая команда. Смещение имеет размер два байта и может быть
как положительным, так и отрицательным. Смещение считается в полусловах,
поэтому при выполнении команды значение смещения умножается на 2 и
добавляется к содержимому счетчика команд. Умножение на 2 выполняется в связи с
тем, что длина команды кратна 2 и не может быть меньше 2 байт. Размер команд
условного перехода — 4 байта.
JL
JZ
JG
JGE
JNZ
JLE
$W,
$W,
$W,
$W,
$W,
$W,
RA;
RA;
RA;
RA;
RA;
RA;
если
если
если
если
если
если
s($W)
$W
s($W)
s($W)
$W
s($W)
< 0,
= 0,
> 0,
>= 0,
!= 0,
<= 0,
то
то
то
то
то
то
PC
PC
PC
PC
PC
PC
<<<<<<-
(PC
(PC
(PC
(PC
(PC
(PC
+
+
+
+
+
+
2*RA)
2*RA)
2*RA)
2*RA)
2*RA)
2*RA)
mod
mod
mod
mod
mod
mod
232
232
232
232
232
232
Регистр равен нулю, если все биты — нулевые; регистр не равен нулю, если хотя бы
один бит (даже знаковый) — не нулевой; значение в регистре больше нуля, если
знаковый разряд (старший) равен 0; значение в регистре меньше нуля, если значение
знакового разряда равно 1. Эти условия одинаковы как для целых, так и для дробных
чисел, поэтому эти команды работают и для целых чисел и плавающих чисел.
Две следующие команды используются исключительно для проверки целых чисел.
JOD $W, RA;
JEV $W, RA;
если s($W) mod 2 = 1, то PC <- (PC + 2*RA) mod 232
если s($W) mod 2 = 0, то PC <- (PC + 2*RA) mod 232
Целое значение в регистре является четным, если младший разряд равен 0; значение
в регистре является нечетным, если младший разряд равен 1. Эти условия не зависят
от того, является ли число положительным или отрицательным.
Виртуальная машина имеет несколько команд безусловного перехода.
JMP RA;
JMPR $A;
PC <- (PC + 2*RA) mod 232
PC <- $A
Эти команды выполняют безусловный переход:
 JMP — относительный, по смещению от команды JMP;
 JMPR — косвенный, по абсолютному адресу в регистре;
В команде относительного перехода JMP смещение составляет 3 байта. Длина
команды косвенного перехода занимает в памяти 2 байта, а команда относительного
перехода — 4 байта.
Адрес, находящийся в регистре, естественно, должен быть четным, иначе возникает
прерывание по ошибке адресации.
Команды перехода к подпрограмме имеют такие же формы (и размеры) аргументов.
Дополнительное действие команд CALL состоит в запоминании адреса следующей
команды (адрес возврата) в стеке.
CALL RA;
CALLR $A;
SP-=4; [SP] <- PC; PC <- (PC + 2*RA) mod 232
SP-=4; [SP] <- PC; PC <- $A
Для возврата из подпрограммы применяется команда RET, выполняющая косвенный
переход по содержимому вершины стека. Адрес возврата извлекается из стека.
RET N;
PC <- [SP]; SP+=4; SP+=4*N;
Аргумент команды позволяет «извлечь» из стека N элементов. На самом деле
извлечения не происходит, просто корректируется указатель стека. Длина команды,
как вы можете видеть, составляет 2 байта.
Команды цикла со счетчиком обеспечивают переход заданное количество раз. При
выполнении команды значение счетчика уменьшается на 1 и если значение не равно
0, то выполняется переход. При нулевом значении счетчика переход не выполняется.
Значение в регистре-счетчике считается целым беззнаковым числом.
LOOP $С, RA;
LOOP $С, $X, $Y;
--u($C); если u($C)!= 0, то PC<-(PC+2*RA) mod 232
--u($C); если u($C)!= 0, то PC<-($X + $Y) mod 232
$C – регистр-счетчик; в качестве счетчика используется любой регистр-слово. Первая
команда выполняет относительный переход; размер смещения — 2 байта. Вторая
форма команды выполняет косвенный переход по адресу, вычисляемому как сумма
двух регистров $X и $Y. Длина команд — 4 байта.
К командам перехода относится и команда останова:
STOP N
Команда имеет длину 2 байта. Выполнение этой команды останавливает процессор;
при этом регистр PC содержит адрес команды останова.
Целочисленная арифметика
Набор операций целочисленной арифметики вполне традиционен. Однако все
операции выполняются только в регистрах: VM в этом случае следует традициям
RISC-компьютеров. Большинство команд — трехадресные и имеют размер 4 байта.
Но есть и ряд одноадресных команд, занимающих в памяти 2 байта.
ADD
SUB
MUL
DIV
MOD
$W1,
$W1,
$W1,
$W1,
$W1,
$W2,
$W2,
$W2,
$W2,
$W2,
$W3;
$W3;
$W3;
$W3;
$W3;
s($W1)
s($W1)
s($W1)
s($W1)
s($W1)
=
=
=
=
=
s($W2)+
s($W2)s($W2)×
s($W2)/
s($W2)%
s($W3)
s($W3)
s($W3)
s($W3)
s($W3)
Операция MOD вычисляет остаток от деления $W2 на $W3. Команда DIV выполняет
целочисленное деление. При выполнении команды умножения MUL старшая часть
произведения записывается в следующий после указанного регистра-результата
$w1+1.
При выполнении этих операций может произойти переполнение. В этом случае в PSW
устанавливается флаг целочисленного переполнения OF. Результат в регистр
записывается всегда. Если разрешено прерывание, то оно выполняется после записи
результата.
Регистры в команде могут быть одни и те же:
ADD $W1, $W1, $W1
Эта команда фактически выполнит умножение числа в регистре $W1 на 2.
Аналогичные пять команд выполняют арифметические действия с беззнаковыми
целыми числами. Например, команда сложения имеет формат:
ADDU $W1, $W2, $W3;
u($W1) = (u($W2)+ u($W3)) mod 232
Эти команды обычно используются для манипуляции с адресами, так как действия
выполняются по модулю 232. Прерывания при переполнении никогда не происходит,
однако в PSW устанавливается флаг CF.
Команды знаковой и целочисленной арифметики могут быть записаны в формате с
непосредственным операндом:
ADDI $W1, iS;
s($W1) = (s($W1)+ s(iS))
И в командах беззнаковой арифметики может быть задан непосредственный операнд,
например:
ADDUI $W1, iS;
u($W1) = (u($W1)+ u(iS)) mod 232
В процессе операции непосредственный аргумент расширяется до размера слова.
Арифметические команды с непосредственными операндами выполняют действия
только с регистрами-словами.
В состав основного множества операций, естественно, входят и команды сравнения.
CMP $W1, $W, $W3;
s($W) = +1, 0, -1
Команда сравнения CMP устанавливает регистр $W равным:
 +1, если s($W1) > s($W3);

0, если s($W1) = s($W3);
 -1, если s($W1) < s($W3);
Таким образом, команда сравнения позволяет «забить» любой регистр единицами
(это значение -1 в дополнительном коде) или нулями.
Команда беззнакового сравнения CMPU выполняет те же действия с беззнаковыми
целыми числами. Команды сравнения могут быть записаны с непосредственным
операндом:
CMPI $W1, $W, iB;
CMPUI $W1, $W, iB;
Эти команды выполняются только с регистрами-словами. В процессе операции
непосредственный аргумент расширяется до размера слова. Эти команды добавлены
только для удобства. Например, для сравнения с нулем не нужно будет очищать
регистр или загружать в регистр 0.
Арифметическая группа команд включает и одноадресные команд:
NEG $W;
s($W) <- -s($W)
Эта команда просто вычисляет дополнительный код числа, находящегося в регистре
$W. Если в регистре содержится наименьшее отрицательное число, то преобразование
не выполняется, и в PSW устанавливается флаг переполнения OF. Возникновение
прерывания зависит от маски разрешенных прерываний. Если в регистре находится
ноль, то никаких действий не выполняется.
ABS $W;
если s($W) < 0, то s($W) <- -s($W)
Команда вычисляет абсолютное значение целого знакового числа. Если число
положительное, то оно не изменяется, если число отрицательное, то вычисляется
дополнительный код. Если в регистре содержится наименьшее отрицательное число,
то команда работает аналогично команде NEG. Ноль остается нулем.
Команды инкремента увеличивают и уменьшают значение регистра на единицу. При
переполнении они работают аналогично соответствующим командам сложения.
INC $W;
INCU $W;
DEC $W;
DECU $W;
s($W)
u($W)
s($W)
u($W)
<<<<-
s($W) + 1
(u($W) + 1) mod 232
s($W) - 1
(u($W) - 1) mod 232
Изменение переменных на единицу настолько часто встречается в практике
программирования, что наличие этих команд среди множества базовых операций
является практически обязательным. К тому же эти команды занимают всего два
байта памяти, а не 4, как команды сложения и вычитания.
Дробная арифметика
Арифметика с дробными числами тоже выполняется только на регистрах. В команде
задаются только длинные регистры — вся арифметика выполняется с двойной
точностью.
FADD
FSUB
FMUL
FDIV
FMOD
FCMP
$D1,
$D1,
$D1,
$D1,
$D1,
$D1,
$D2, $D3;
$D2, $D3;
$D2, $D3;
$D2, $D3;
$D2, $D3;
$W, $D3;
d($D1) = d($D2) +
d($D1) = d($D2) d($D1) = d($D2) ×
d($D1) = d($D2) /
d($D1) = d($D2) %
s($W) = +1, 0, -1
d($D3)
d($D3)
d($D3)
d($D3)
d($D3)
Деление, естественно выполняется дробное. Остаток от деления тоже представляется
в формате дробного числа и вычисляется как
делимое – n * делитель
Здесь n является ближайшим целым к частному делимое/делитель.
В команде сравнения результат записывается точно в такой же форме, что и в
командах CMP и CMPU.
Кроме арифметики в состав основного множества операций обычно включается ряд
функций. Практически все функции-операции — одноадресные, поэтому занимают
всего два байта, хотя и выполняют сложную работу. Результат помещается на место
аргумента в заданный регистр.
FSGN $D;
FINT $D;
FRND $D;
// изменение знака
// $D = целая часть числа
// округление до целого
Команда FSGN меняет знак числа на противоположный. Команда FINT отсекает
дробную часть и оставляет в регистре целую часть число в дробном формате.
Команда FRND выполняет правильное округление до целого и сохраняет результат в
том же регистре в плавающем формате.
Виртуальная машина выполняет минимальный набор стандартных функций, с
помощью которых можно вычислить другие родственные функции.
FSQRT $D;
FEXP $D;
FSIN $D;
FATAN $D;
FLN $D;
//
//
//
//
//
квадратный корень
e^x
sin(x)
arctg(x)
ln(x)
Названия функций говорят сами за себя. Аргумент задается в регистре, результат
помещается в тот же регистр. Естественно, при неверном задании аргумента
(например, ноль для логарифмических функций) операция не выполняется и
аргумент не изменяется. Возникновение прерывания зависит от маски прерываний.
В плавающей арифметике часто используются константы 1.0, 2.0, число е и число π.
Система команд содержит ряд команд загрузки этих констант в регистры:
FLD1 $D;
FLD2 $D;
$D = 1.0
$D = 2.0
FLDPI $D;
FLDE $D;
$D = 3.14159265358979323846
$D = 2.71828182845904523536
// число Пи
// число е
Битовые операции и сдвиги
В битовых операциях слово х рассматривается как вектор v(x) из 32 отдельных битов.
Учебная виртуальная машина включает следующие типичные битовые команды:
OR $W1, $W2, $W3;
AND $W1, $W2, $W3;
XOR $W1, $W2, $W3;
// поразрядное логическое сложение (ИЛИ)
// поразрядное логическое умножение (И)
// сложение по модулю 2 (ИСКЛЮЧАЮЩЕЕ ИЛИ)
Эти три команды можно записывать в форме с непосредственным операндом. В этом
случае используются регистры-байты.
ORI $B1, $B2, iB;
ANDI $B1, $B2, iB;
XORI $B1, $B2, iB;
// поразрядное логическое сложение (ИЛИ)
// поразрядное логическое умножение (И)
// сложение по модулю 2 (ИСКЛЮЧАЮЩЕЕ ИЛИ)
Все эти команды занимают в памяти 4 байта. Команда битового отрицания
NOT $W
инвертирует биты в регистре $W и имеет размер 2 байта.
Типичными командами этой группы являются и команды сдвига. Обычно различают
логический и арифметический сдвиг. В учебной машине таких команд всего 4 и они
тоже работают со словами в регистрах. Команды логического сдвига:
SHU $W1, $W2, $B;
SHUI $W1, $W2, iB;
u($W1)= (u($W2)×2s($b))
u($W1)= (u($W2)×2ib)
В первом варианте регистр $W2 сдвигается на заданное в байтовом регистре $B
количество разрядов, и результат записывается в $W1. Направление сдвига
определяется знаком числа, записанного в регистре $B. Если число положительное,
то выполняется сдвиг влево, при отрицательном значении — сдвиг вправо.
Во втором варианте количество сдвигов задается прямо в команде непосредственным
аргументом. Направление сдвига определяется знаком аргумента. Последний
выдвигаемый разряд устанавливает в PSW флаг CF. Освобождающиеся разряды
заполняются нулями.
Две аналогичные команды выполняют арифметический сдвиг:
SH $W1, $W2, $B;
SHI $W1, $W2, iB;
s($W1)= (s($W2)×2s($b))
s($W1)= (s($W2)×2ib)
Арифметический сдвиг фактически означает умножение и деление на степень
двойки. Поэтому сдвиг влево может привести к переполнению. Для положительных
чисел арифметический сдвиг вправо не отличается от логического сдвига. А вот для
отрицательных чисел при сдвиге вправо освобождающиеся разряды заполняются
единицей — знаковым разрядом. Такое положение имеет одно важное следствие, о
котором не следует забывать: если в регистре $W2 записана -1, то арифметический
сдвиг вправо фактически не изменяет это значение. На рис. 1.6 показано выполнение
арифметического сдвига вправо на 1 разряд.
знак
CF
Рис. 1.6. Арифметический сдвиг вправо на 1 разряд
Крайняя правая 1 «выдвигается» из регистра и попадает в CF, а слева «вдвигается»
тоже 1, поэтому значение в регистре не изменяется. Как вы понимаете, от количества
сдвигов положение не зависит.
Стековые операции
Базовый набор стековых операций включает несколько команд пересылки между
стеком и регистрами, между стеком и памятью, а также набор арифметических
команд, сохраняющих результат либо в регистре, либо в стеке. Все команды,
естественно, короткие, так как первый аргумент находятся в стеке. Так как указатель
стека изменяется только на 4, для работы со стеком в основном наборе команд
реализована только арифметика с короткими дробными числами.
PUSH $W;
PUSHM $A;
SP+=4; [SP] = $W
SP+=4; [SP] = [$A]
Команды помещают на вершину стека слово. Симметричные команды
POP $W;
POPM $A;
$W = [SP]; SP-=4;
[$A] = [SP]; SP-=4;
извлекают из стека и помещают слово в регистр или в память.
Две команды копируют содержимое вершины стека, не извлекая его из стека.
STSP $W;
STSPM $A;
$W = [SP];
[$A] = [SP];
Пять команд выполняют арифметические действия с короткими дробными числами.
Один аргумент находится в стеке, второй в регистре, результат сохраняется в стеке
на месте первого аргумента.
FADDS
FSUBS
FMULS
FDIVS
FMODS
$W;
$W;
$W;
$W;
$W;
f([SP])=
f([SP])=
f([SP])=
f([SP])=
f([SP])=
f([SP])+
f([SP])f([SP])×
f([SP])/
f([SP])%
f($W)
f($W)
f($W)
f($W)
f($W)
Аналогичные 5 команд выполняют действия и запоминают результат не в стеке, а в
регистре, например:
FADDR $W;
f($W)= f([SP])+ f($W)
Наличие стековой арифметики является элементом ROSC-архитектуры. Такие
команды интенсивно используются в компиляторах. И арифметические выражения
удобно вычислять, используя стек. В основной набор входит только минимальное
множество команд, с помощью которых удобно вычислять арифметические
выражения. Большинство стековых команд входят в расширенный набор, который
мы рассмотрим в последующем.
Загрузка-сохранение
Команды пересылки — это, в первую очередь, команды загрузки и сохранения
регистров. Виртуальная машина обрабатывает 4 размера целых чисел, поэтому для
каждого размера в системе команд реализованы соответствующие команды загрузки
и сохранения. Символические коды команд загрузки обозначаются так:
 LDB, LDBA — загрузка байта;
 LDS, LDSA — загрузка полуслова;
 LDW, LDWA — загрузка слова;
 LDD, LDDA — загрузка двойного слова;
Команды двухадресные, и в них качестве первого аргумента может быть задан
регистр соответствующего размера. Второй аргумент представляет собой адрес
памяти, который задается либо в абсолютной, либо в регистровой форме. Например,
загрузка регистров-полуслов выглядит так:
LDSA $S, A;
LDS $S, $X, $Y;
$S <- M2[A]
$S <- M2[($X+$Y)]
-- абсолютная
-- регистровая
Команды просто копируют соответствующее количество байт из памяти в регистр.
Поэтому команды загружают как знаковые, так и беззнаковые целые числа. Кроме
того, команды LDW позволяет загружать короткое, а команды LDD — длинное дробное
число.
LDWA
LDW
LDDA
LDD
$W,
$W,
$D,
$D,
A;
$X, $Y;
A;
$X, $Y;
$W
$W
$D
$D
<<<<-
M4[A]
M4[($X+$Y)]
M8[A]
M8[($X+$Y)]
Симметричные команды сохранения копируют соответствующее количество байт в
из регистра в память, например:
STBA $B, A;
STD $D, $X, $Y;
M1[A] <- $B
M8[($X+$Y)] <- $D
Все команды загрузки и сохранения с аргументом-адресом имеют размер 6 байт.
Регистровая форма имеет размер 4 байта.
Три команды позволяют загрузить в регистр непосредственный операнд:
LDBI $B, iS;
LDSI $S, iS;
LDWI $W, iW;
$B <- младший байт iS
$S <- iS
$W <- iW
Команды LDBI и LDSI занимают в памяти 4 байта. В команде LDBI в регистр
записывается младший байт непосредственного аргумента, который занимает в
команде 2 байта.
Команда LDWI имеет размер 6 байт. Команда LDWI имеет второе мнемоническое
название кода операции — LA, что означает «загрузка адреса». Непосредственная
константа iW в этой команде может считаться абсолютным адресом памяти.
К командам загрузки можно отнести и команды очистки регистра CLRi, которые
занимают два байта памяти:
CLRB
CLRS
CLRW
CLRD
$B
$S
$W
$D
$B
$S
$W
$D
<<<<-
0
0
0
0
Команды кажутся избыточными в системе команд, так как обнулить регистр можно
разными способами, например, с помощью команды непосредственной загрузки
регистров. Однако команда загрузки занимает в памяти не менее 4 байтов. Кроме
того, команды очистки регистра-слова и двойного слова могут использоваться с
плавающей арифметикой, так как представление нуля одинаково для дробных и
целых чисел.
Несколько команд, естественно, необходимы, чтобы загружать системные регистры.
LDSP $A;
LDRI $A;
LDPSW $W;
STPSW $W;
SP <- $A
RI <- $A
$W <- PSW
PSW <- $W
Учитывая то, что в одних и тех же регистрах размещаются и целые, и дробные числа,
необходимы операции загрузки регистров с преобразованием из целого в дробное
число. Пересылки с преобразованием аргументов относятся к расширенному набору
операций и будут рассмотрены далее.
Ввод-вывод
Ввод-вывод учебной вычислительной машины основан на концепции портов. Порт
— это регистр на внешнем устройстве. Каждый порт имеет номер. В программе
номер порта записывается как беззнаковое полуслово — таким образом, общее
количество портов не превышает 65536, причем нумерация начинается с 0. Порт
задается либо в виде непосредственной константы, либо помещается в регистрполуслово $S.
Вообще-то говоря, ввод-вывод в реальных условиях практически никогда не
выполняется с помощью процессорных команд. Уже давно вводом-выводом в
реальных компьютерах «заведует» операционная система, в состав которой входят
драйверы различных устройств. Поэтому команды ввода-вывода VM определены
только для полноты картины. Однако эти команды позволяют моделировать вводвывод низкого уровня, выполняя «физический» обмен данными между памятью
виртуальной машины и «устройством».
Будем считать, что ввод из нулевого порта означает ввод символов с клавиатуры, а
вывод в нулевой порт — вывод символов на экран:
IN $B;
OUT $B;
// ввод байта с клавиатуры
// вывод байта на экран
Обращение с нулевым портом выполняется по умолчанию, и определяется кодом
операции. Так порт ввода-вывода не указывается, команды имеют длину 2 байта.
Помимо этого нам потребуются команды ввода-вывода «высокого» уровня. По
аналогии с абстрактными машинами AMF и AMS определим команды ввода-вывода
коротких и длинных целых и дробных чисел:
IIN
IIND
FIN
FIND
IOUT
IOUTD
FOUT
FOUTD
$W
$D
$W
$D
$W
$D
$W
$D
//
//
//
//
//
//
//
//
ввод целого с клавиатуры
ввод длинного целого с клавиатуры
ввод короткого вещественного с клавиатуры
ввод длинного вещественного с клавиатуры
вывод целого на экран
вывод длинного целого на экран
вывод короткого вещественного на экран
вывод длинного вещественного на экран
Дополнительный набор команд
Дополнительный набор команд включает еще 256 команд с двухбайтным кодом
операции: первый байт равен FF16, второй определяет операцию. Однако в данном
разделе мы определим только несколько двухадресных команд пересылки. Читатель
может самостоятельно определить необходимые ему операции.
Пересылки
Группа команд обеспечивает пересылки между регистрами, перевод целых в дробные
и обратно, а также преобразование целых меньших размеров в большие. Команды
входят в расширенный набор операций с двухбайтным кодом операции; первый байт
всех команд равен FF16. Все команды двухадресные и занимают в памяти 4 байта.
Эти команды, как и команды загрузки-сохранения основного набора, работают со
всеми видами целых, поэтому для каждой группы из 4 команд будем описывать
общий формат. Четыре команды SWPx (x = B,S,W,D) выполняют обмен содержимого
указанных регистров:
SWPx $R1, $R2;
Регистры должны быть одинакового размера.
Команды MOVx выполняют копирование регистра $R2 в регистр $R1:
MOVx $R1, $R2;
Регистры тоже должны быть одинакового размера.
Несколько команд выполняет увеличение
осуществляя расширение знакового разряда.
CBS $S, $B
CSW $W, $S
CWD $D, $W
размера
целых
знаковых
чисел,
// полуслово <- байт
// слово <– полуслово
// слово -> двойное слово
Несколько команд реализуют переводы целых чисел в дробные. Всего таких команд
8 в соответствии с количеством сочетаний коротких и длинных целых с короткими и
длинными дробными. Например, команда
CLF $W, $D
выполняет преобразование длинного целого в регистре $D в короткое дробное с
пересылкой результата в регистр $W. Симметричная ей команда
CFL $D, $W
выполняет обратное преобразование короткое дробное → длинное целое. Команда
CWD $D, $W
осуществляет перевод из целого-слова в длинное дробное число, а симметричная ей
команда
CDW $W, $D
осуществляет обратное преобразование. Естественно, в командах преобразования из
дробного числа в целое может произойти переполнение.
И наконец, две команды выполняют преобразования дробных чисел. Команда CFD,
имеющая формат
CFD $D, $W
осуществляет преобразование короткого дробного в регистре-слове в длинное
дробное число, а команда CDF, имеющая формат
CDF $W, $D
выполняет обратное преобразование. При этом может произойти переполнение. В
этом случае результат не записывается, а возникновение прерывания зависит от
маски прерываний, установленной в PSW.
Все регистровые команды преобразования имеют длину 4 байта.
Пример программы для виртуальной машины
VM
Для демонстрации возможностей VM реализуем тот же пример, который представлен
выше для абстрактных машин AMF и AMS. Пусть размер памяти — 64 килобайта и
программа записана в память с нулевого байта. Регистры записаны без префикса $, а
команды – строчными буквами. Программа представлена в табл. 1.511.
Таблица 1.5.
Адрес
Команда
00000
ldwi
w10
00006
clrw
w11
00008
ldwi
w12
Комментарий
800
Адрес массива в регистре w10
Очистка индекса
100
Счетчик повторений цикла = 100
В программе есть одна ошибка: при некоторых входных данных программа
завершится аварийно. Поиск и упражнение ошибки оставляем читателю в качестве
упражнения (см. «Контрольные вопросы и упражнения»).
11
00014
fld1
d1
Загрузка 1.0
00016
clrd
d30
S=0
00018
find
d20
Ввод(x[i])
00020
std
d20
w10
w11
Запись x[i] в память
00024
fcmp
d20
w5
d1
x[i] > 1? w5 = -1,0,+1
00028
jge
w5
6
00032
fdiv
d20
d1
d20
d20 = 1/x[i]
00036
fadd
d30
d30
d20
S = S + 1/x[i]
00040
incui
w11
8
Переадресация: на следующий x[i+1]
00044
loop
w12
-13
Цикл: назад на команду ввода
00048
foutd
d30
00050
stop
0
Нет: на переадресацию
вывод
Проанализируем эту программу. Единицей памяти является байт, поэтому адреса
команд изменяются не на 1, а на размер команды в байтах. По той же причине адрес
массива для хранения величин x[i] является адресом байта, а не элемента массива.
Так как размер элемента массива равен 8 (double), то команда переадресации (адрес
00040) изменяет индекc на 8, а не на 1.
В связи с наличием регистров не потребовалось изменять команды в памяти для
переадресации: все вычисления выполняются в регистрах. Кроме того, команда
цикла (адрес 00044) автоматически вычитает 1 из счетчика и проверяет новое
значение на ноль. Наличие регистров и соответствующих команд (адреса 00014 и
00016) позволяет избавиться от хранения констант 0 и 1 в памяти.
Отдельно нужно сказать об аргументах команд перехода jle и цикла loop. В первом
случае переход осуществляется вперед, поэтому аргумент-смещение является
положительным. Напомним, что вычисляется аргумент расстояние в полусловах от
команды перехода до целевой команды:
(адрес целевой команды) - (адрес команды перехода) / 2
Поэтому в данном случае аргумент будет таким:
RA = (00040 – 00028) / 2 = 6
Команда цикла переход выполняется назад, поэтому аргумент отрицательный.
Вычисляя по той же формуле, получаем следующее:
RA = (00018 – 00044) / 2 = -13
Общий размер программы занимает всего 52 байта, в то время как программы для
AMF и AMS занимали по 18 слов, каждое из которых размером 4 байта (32 бита).
Таким образом, можно сделать вывод, что наличие регистров довольно существенно
сокращает размер программы.
Резюме
Системное программное обеспечение разрабатывается для конкретного процессора,
поэтому требуется учитывать архитектуру команд. В настоящее время наибольшее
распространение получили архитектуры типа CISC, RISC и ROSC. Важнейшими
составляющими архитектуры являются: типы данных, организация памяти, состав
регистров, набор команд и способы адресации аргументов. В этом смысле системное
ПО является машинно-зависимым. С другой стороны, многие системные программы
фактически никак от архитектуры не зависят. Четкое отделение машинно-зависимых
частей повышает переносимость программного обеспечения. Для тех же целей
применяются виртуальные машины. Кроме того, виртуальная машина в качестве
целевой машины при трансляции существенно сокращает трудозатраты при
разработке системного ПО и обеспечивает совместимость на уровне исполняемых
файлов. В данной главе разработана архитектура трех абстрактных машин: AMF,
AMS и VM. Сравнительный анализ программ показывает, что наличие регистров в
архитектуре позволяет сократить общий объем программ.
Контрольные вопросы и упражнения
1.
Объясните, что такое «машинная зависимость», и какие программы считаются
машинно-зависимыми.
2.
Перечислите важнейшие особенности архитектуры команд.
3.
Чем отличается RISC-архитектура от CISC-архитектуры?
4.
Каковы достоинства и недостатки использования виртуальной машины?
5.
Приведите примеры промышленных виртуальных машин.
6.
Какие способы реализации исполнителя команд виртуальной машины вы знаете?
7.
Объясните, что означает термин «мобильность программного обеспечения».
8.
9.
Какие целые типы используются в виртуальной машине VM и насколько они
типичны?
Каковы функции верификатора в составе виртуальной машины JVM?
10. Определите набор битовых операций and, or, not, xor для абстрактной машины
AMS.
11. Покажите признаки ROSС-архитектуры в учебной виртуальной машине VM.
12. Объясните, что такое фрейм стека.
13. Сколько форматов команд реализовано в учебной виртуальной машине?
14. Какие методы адресации аргументов используются в командах виртуальной
машины VM?
15. Какие группы команд реализованы в составе системы команд VM?
16. В чем команды перехода принципиально отличаются от других команд?
17. Покажите признаки RISС-архитектуры в учебной виртуальной машине.
18. Чем отличается абсолютный переход по адресу от относительного?
19. Объясните, почему промышленные абстрактные машины имеют ROSCархитектуру.
20. Почему наличие регистров сокращает общий размер программы?
21. Каким образом можно фактически бесконечно расширять набор команд
виртуальной машины?
22. Какие группы команд определены в составе системы команд машин AMF и
AMS?
23. Найдите и исправьте ошибку в программе для виртуальной машины VM.
24. Определите набор безадресных стековых операций для VM.
25. Объясните принцип little-endian.
Глава 1. Системное программное обеспечение и архитектура ЭВМ ..............................1
Реальные и виртуальные компьютеры ...........................................................................2
Виртуальные машины и промышленное программирование ..................................4
Промышленные виртуальные машины ..........................................................................6
P-машина .......................................................................................................................7
Java Virtual Machine ...................................................................................................12
Common Language Runtime ....................................................................................... 16
Разработка виртуальных машин ................................................................................... 16
Абстрактная машина AMF ........................................................................................ 16
Абстрактная машина AMS ........................................................................................ 19
Примеры программ ....................................................................................................22
Учебная виртуальная машина VM ................................................................................ 24
Память и данные ........................................................................................................24
Регистры ...................................................................................................................... 27
Система команд ..........................................................................................................28
Пример программы для виртуальной машины VM .................................................... 39
Резюме ............................................................................................................................. 41
Контрольные вопросы и упражнения ...........................................................................41
Download