Программа является машинно-зависимой, если при ее разработке необходимо учитывать особенности архитектуры. Например, генератор кода в любом компиляторе является машинно-зависимой частью
Вид материала | Программа |
СодержаниеПромышленные виртуальные машины Java Virtual Machine Common Language Runtime |
- Примерная инструкция по охране труда при подготовке к работе сельскохозяйственных машин, 301.45kb.
- Рабочая программа дисциплины «Машинно-зависимые языки программирования» Направление, 136.33kb.
- Качественная работа машинно-тракторного пахотного агрегата позволяет хорошо подготовить, 410.19kb.
- И программа 13-ой Международной научно-практической конференции машинно-технологическое, 257.75kb.
- Е. А. Цветков Московский физико-технический институт (государственный университет), 381.58kb.
- Неотъемлемой частью работ при разработке или модификации программных систем является, 22.36kb.
- Одним из эффективных математических методов для определения зависимости по множеству, 139.29kb.
- А тесноту связи между зависимой и независимой переменными, 526.21kb.
- Вопросы по курсу «Допглавы эконометрики» Преподаватель : доцент, к ф. м н. Пяткина, 7.26kb.
- Контрольная работа по гражданской обороне, 433.65kb.
Промышленные виртуальные машины
В качестве одного из первых примеров абстрактной машины можно назвать 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.
P-машина
В системе UCSD-Pascal результатом трансляции2 исходной Pascal-программы является программа на P-коде, которая состоит из двух компонент: массив кодов и структура, представляющая модель памяти (рис. 1.1) для данной Р-программы.
Рис.1.1. Структура памяти Р-программы
Модель памяти содержит статическую часть, где записаны константы, и динамическую часть, где размещаются стек и куча. Структура динамической памяти представлена на рис. 1.2. Для работы со стеком и кучей процессор P-машины содержит несколько регистров, в которых записаны адреса динамической памяти:
- PC — счетчик адреса команд;
- SP — указатель стека процедуры;
- MP — указатель начала стекового фрейма;
- NP — указатель кучи;
- EP — указатель границы общего стека в динамической памяти.
Рис. 1.2. Структура динамической памяти Р-машины
Для каждого вызова процедуры (в том числе и рекурсивного) в общем стеке выделяется стековый фрейм, структура которого показана на рис. 1.3. В этом фрейме, начало которого отмечает указатель MP, выделяется место для локального стека, вершину которого определяет указатель SP. Другой конец фрейма записан в регистре EP. Указатель MP отмечает начало служебной части фиксированного размера, с помощью которой обеспечиваются связи с вызывающей процедурой:
- static link обеспечивает доступ данной процедуры к переменным объемлющей процедуры3;
- dynamic link — это связь с фреймом стека вызывающей процедуры; содержит значение MP вызывающей процедуры;
- previous EP хранит значение EP вызывающей процедуры;
Здесь же, в служебной части размещается адрес возврата и выделяется место для возвращаемого значения функции. После фиксированной части помещаются параметры процедуры (если есть), локальные переменные (если есть), и выделяется место для локального стека.
Рис.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 L2
код для выполнения оператора
UJP L1
L2
В систему команд входят команды вызова стандартных и пользовательских процедур. Требуемые параметры, естественно, должны быть предварительно помещены в стек. Например, команда 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 являются:
- Загрузчик классов, который загружает, связывает и инициализирует классы;
- Исполнитель (интерпретатор), который и выполняет программу в байт-кодах;
- Интерфейс потоков, управляющий параллельной работой;
- Модуль управления памятью, который управляет кучей, где хранятся объекты и массивы;
- Модуль управления обработкой исключительных ситуаций, систематически отслеживающий возникающие в процессе выполнения исключительные ситуации и ошибки;
- Модуль управления защитой, который препятствует запуску «враждебных» программ.
Каждый класс-файл содержит байт-код всех описанных методов, таблицу имен, таблица связей с суперклассами и т.д. Вся эта информация записана в единственной структуре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 представляют собой размеры (количество элементов) массивов соответствующего типа, представленных в этой же структуре.
Для эффективной работы 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 dconst_0
1 dstore_1
2 goto 9
5 dload_1
6 dconst_1
7 dadd
8 dstore_1
9 dload_1
10 ldc2_w #4
13 dcmpg
14 iflt 5
Первые три команды реализуют инициализацию переменной i, следующие четыре команды — операция инкремента переменной i. И наконец, последние четыре — проверку выражения в условии оператора while. Числа слева — это смещение в байтах соответствующей команды. Эти смещения задаются в командах перехода в качестве операндов: в команде безусловного перехода goto и в команде условного перехода iflt.
Тот же цикл с целой переменной i:
int i = 0;
while (i < 100)
i++;
Эти операторы транслируются в значительно более короткую последовательность команд:
0 iconst_0
1 istore_1
2 goto 8
5 iinc 1 1
8 iload_1
9 bipush 100
11 if_icmplt 5
Инициализация переменной тоже выполняется тремя командами, а вот инкремент осуществляется одной командой. Последние три команды — это проверка значения i и условный переход.