.: [предыдущая | оглавление | следующая] :.

15.5 Виртуальные машины

15.5.1 Введение

Под виртуальной машиной (ВМ) будем подразумевать программную реализацию модели некоторой вычислительной системы. Практически все известные системы программирования можно рассматривать как виртуальные машины, поскольку часть операций моделируются, т.е. для них написан программный код. При построении ВМ необходимо рассмотреть следующие вопросы:
- структура ВМ;
- множество элементарных данных;
- множество элементарных операций;
- управление последовательностью действий;
- управление данными;
- управление памяти .

Структура ВМ определяется наличием интерпретатора команд и специальных блоков памяти, где хранятся данные, программы и вспомогательная информация. Множество элементарных данных и операций определяется из анализа состава функций и задач некоторой предметной области. Управление последовательностью действий включает два аспекта: управление последовательностью операций и управление вызова подпрограмм. Вопросы управления данными подразумевает рассмотрение способов хранения и передачи данных между подпрограммами.

При создании вычислительного процесса данные могут накапливаться, изменяться и удаляться, поэтому необходимо рассмотреть также вопросы управления памятью.

При построении ВМ для данной предметной области необходимо иметь состав функций, который может быть выявлен в ходе проведения системного анализа с целью построения модели предметной области. Если этот состав конечен и полон,
Таблица 15.4: Интерпретатор выполняет следующие команды
мнемоника команды операнд 1 операнд 2
in addr ввод значения
out addr вывод значения
add addr1 addr2 сложить
sub addr1 addr2 вычесть
mul addr1 addr2 умножить
div addr1 addr2 разделить
jump addr1 безусловный переход
grt addr1 addr2 больше
lst addr1 addr2 меньше
bra addr1 условный переход
stop
test addr равно нулю
mov addr1 addr2 копировать данные
iload addr1 addr2 addr3 загрузить данные косвенно
isave addr1 addr2 addr3 сохранить данные косвенно
inc addr инкремент
dec addr декремент

т.е. все множество алгоритмов в данной предметной можно получить из данного состава функций и при этом множество этих алгоритмов достаточно велико или бесконечно, то необходимо строить ВМ.

15.5.2 Простая виртуальная машина

Виртуальная машина состоит из интерпретатора команд, буфера для хранения кода программы и буфера для хранения данных. Каждая команда имеет фиксированную длину и состоит из кода операции и операндов. Код операции это число идентифицирующее операцию. Код операции занимает один байт. Операнд указывает на ячейку памяти, где хранится необходимая информация или куда будет занесен результат операции. В команде может быть несколько операндов. Например, для команды «ADD» необходимо указать адрес первого слагаемого и адрес второго слагаемого. Результат сложения будет занесен по адресу первого слагаемого. Все операнды имеют размер одного байта. Тогда всю программу можно размесить в байтовом массиве. Поскольку адрес записывается в один байт то размер буферов для данных и программы не должен превышать 256. Алгоритм интерпретатора команд достаточно простой:
1) счетчик команд (pc) сбрасывается в ноль;
2) читается очередной код команды (code=prog[pc]);
3) по коду происходит переход на обработку заданной команды;
4) производится выборка операндов;
5) выполняется операция, запоминается результат.
Таблица 15.5: Таблица кодов команд виртуальной машины
#define IN 1 //ввод числа
#define OUT 2 //вывод числа
#define ADD 3 //сложить
#define SUB 4 //вычесть
#define MULT 5 //умножить
#define DIV 6 //разделить
#define JUMP 7 //безусловный переход
#define GRT 8 //проверка на больше
#define LST 9 //проверка на меньше
#define BRA 10 //перейти если флаг установлен
#define STOP 11 //останов
#define TEST 12 //проверка на ноль
#define MOV 13 //переслать данные
#define ILOAD 14 //косвенная загрузка данных
#define ISAVE 15 //косвенное соранение данных
#define INC 16 //увеличить на единицу
#define DEC 17 //уменьшить на единицу

6) корректируется счетчик команд в соответствии с количеством операндов;
7) происходит переход на выполнение шага 2.

15.5.3 Реализация простой виртуальной машины


#define SIZEMEMORY 256 //размер памяти под данные
#
define DEBUG_SVM//работа программы в отладочном режиме
int Mem[SIZEMEMORY]; //память по данные

Входные параметры :
prog - массив, содержащий программу (последовательность команд).
Mem - массив, содержащий данные.

Основные переменные:
com - код команды;
Addr, Addr2, Addr3 - вспомогательные переменные для хранения операндов текущей команды.
flag - переменная, хранящая флаг перехода.
pc - счетчик команд.


int SimpleVirtualMachine(unsigned char *prog,int Mem[])
{
     
int com, //код команды
     
Addr,Addr2,Addr3, //вспомогательные ячейки хранения адресов
      flag=0, // флаг перехода
      pc=0; //счетчик команд (program counter)
      while(1) //цикл выполнения программы
      {
            #ifdef DEBUG_SVM //
для отладки
                  PrintMemory();
                  PrintCommand(&prog[pc]);
                  if(getch()==27) exit(0); //
если нажата ESC завершить программу
            #endif
            com=prog[pc]; //
выделяем код операции
            switch (com)
            {
                  case IN:    //
добавляем 1, т.к. вначале pc
                                   // указывает на IN, а нам необходимо само число
                        Addr=prog[pc+1]; //ввод числа
                        printf("svm_input>");
                        scanf("%d",&Mem[Addr]);
                        pc=pc+2; //
смещаем счетчик команд
                  break;

                  case OUT: //
вывод числа
                        Addr=prog[pc+1];
                        printf("svm_ouput>%d\n",Mem[Addr]);
                        pc=pc+2; //
смещаем счетчик команд
                  break;

                  case ADD: //
сложить два числа
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        //
производим сложение и результат записываем по адресу первого элемента
                        Mem[Addr]=Mem[Addr]+Mem[Addr2];
                        pc=pc+3;
                  break;

                  case SUB: //
вычитание
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        Mem[Addr]=Mem[Addr]-Mem[Addr2];
                        pc=pc+3;
                  break;

                  case MULT: //
умножение
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        Mem[Addr]=Mem[Addr]*Mem[Addr2];
                        pc=pc+3;
                  break;

                  case DIV: //
деление
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        Mem[Addr]=Mem[Addr]/Mem[Addr2];
                        pc=pc+3;
                  break;

                  case JUMP: //
безусловный переход
                        pc=prog[pc+1];
                  break;

                  case TEST: //
проверка на ноль и установка флага
                        Addr=prog[pc+1];
                        if (Mem[Addr]==0) flag=1;
                        else flag=0;
                        pc=pc+2;
                  break;

                  case GRT: //
проверка a>b
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        if (Mem[Addr]>Mem[Addr2]) flag=1; //
установка флага
                        else flag=0; //сброс флага
                        pc=pc+3;
                  break;

                  case LST: //
проверка a < b
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        if (Mem[Addr] < Mem[Addr2])
                             flag=1;
                        else
                             flag=0;
                        pc=pc+3;
                  break;
                 
                  case BRA: //
условный переход
                        Addr=prog[pc+1];
                        if(flag) pc=Addr;
                        else pc=pc+2;
                  break;
                 
                  case INC: //
Инкримент
                        Addr=prog[pc+1];
                        Mem[Addr]++;
                        pc=pc+2;
                  break;
                 
                  case DEC: //
Декримент
                        Addr=prog[pc+1];
                        Mem[Addr]++;
                        pc=pc+2;
                  break;
                 
                  case MOV: //
переслать данные
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        Mem[Addr]=Mem[Addr2];
                        pc=pc+3;
                  break;
                 
                  case ILOAD: //
косвенная загрузка данных
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        Addr3=prog[pc+3];
                        Mem[Addr]=Mem[Addr2+Mem[Addr3]];
                        pc=pc+4;
                  break;
                 
                  case ISAVE: //
косвенное сохранение данных
                        Addr=prog[pc+1];
                        Addr2=prog[pc+2];
                        Addr3=prog[pc+3];
                        Mem[Addr2+Mem[Addr3]]=Mem[Addr];
                        pc=pc+4;
                  break;
                 
                  case STOP:
                        return 1;
                  default:
                        printf("Error command\n");
                        return - 1;
            }
      }
}
//
печать первых 10 ячеек памяти
void PrintMemory()
{
      printf("dump memory \n");
      for(int i=0; i<10; i++)
            printf("%2d %5d\n",i,Mem[i]);
}
//
вывод команды на терминал
int PrintCommand(unsigned char *prog)
{
      int Addr,Addr2,Addr3;
      int pc=0;
      switch (*prog)
      {
            case IN:
                  Addr=prog[pc+1]; //
ввод числа
                  printf(" IN %3d\n",Addr);
            break;

            case OUT: //
вывод числа
                  Addr=prog[pc+1]
                  printf("OUT %3d\n",Addr);
            break;
           
            case ADD: //
сложить два числа
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("ADD %3d, %3d\n",Addr,Addr2);
            break;
           
            case SUB: //
вычитание
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("SUB %3d, %3d\n",Addr,Addr2);
            break;
           
            case MULT: //
умножение
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("MULT %3d, %3d\n",Addr,Addr2);
            break;
           
            case DIV: //
деление
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("DIV %3d, %3d\n",Addr,Addr2);
            break;
           
            case JUMP: //
безусловный переход
                  Addr=prog[pc+1]; //ввод числа
                  printf("JUMP %3d\n",Addr);
            break;
           
            case TEST: //
проверка на ноль и установка флага
                  Addr=prog[pc+1]; //ввод числа
                  printf("TEST %3d\n",Addr);
            break;
           
            case GRT: //
проверка a>b
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("GRT %3d, %3d\n",Addr,Addr2);
            break;
           
            case LST: //
проверка a < b
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("LST %3d, %3d\n",Addr,Addr2);
            break;
           
            case BRA: //
условный переход
                  Addr=prog[pc+1];
                  printf("BRA %3d\n",Addr);
            break;
           
            case INC: //
Инкремент
                  Addr=prog[pc+1];
                  printf("INC %3d\n",Addr);
            break;

            case DEC: //
Декремент
                  Addr=prog[pc+1];
                  printf("DEC %3d\n",Addr);
            break;
           
            case MOV: //
переслать данные
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  printf("MOV %3d, %3d\n",Addr,Addr2);
            break;
           
            case ILOAD: //
косвенная загрузка данных
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  Addr3=prog[pc+3];
                  printf("ILOAD %3d, %3d, %3d\n",Addr,Addr2,Addr3);
            break;
           
            case ISAVE: //
косвенное сохранение данных
                  Addr=prog[pc+1];
                  Addr2=prog[pc+2];
                  Addr3=prog[pc+3];
                  printf("ILOAD %3d, %3d, %3d\n",Addr,Addr2,Addr3);
            break;
           
            case STOP:
                  printf("STOP\n");
                  return 1;
            default:
                  printf("Error command\n");
                  return -1;
      }
}
//
Пример1
//
Распределение памяти
#define X 0
#define Y 1
#define SUM 2
//
программа для виртуальной машины (SVM)
//
ввод x, y; вычисдение sum=x+y, вывод sum
unsigned char prog1[]= {
      IN, X, //
ввести значение для X
      IN, Y, //ввести y
      MOV, SUM, X, //переслать значение X в SUM
      ADD, SUM, Y, //найти сумму
      OUT, SUM, //вывести сумму
      STOP //остановить
};
//
Пример2
//
распределение памяти
#define COUNT 0
#define NMAS 1
#define SMAS 2
#define CURE 3
#define MAS 4
#define BEG 0
//
программа2 для виртуальной машины (SVM)
//
нахождение суммы элементов массива
//
для(i=0; i<n; i++) sum=sum+mas[i]; вывод(sum)
unsigned char prog2[]= {
      ILOAD, CURE,MAS,COUNT, //cure=mas[count]
      ADD, SMAS,CURE, //smas=smas+cure
      INC, COUNT, //count=cur+1
      LST, COUNT,NMAS, //count<nmas?
      BRA, BEG, //
переход по BEG
      OUT, SMAS, //вывод суммы(smas)
      STOP //останов
};

void SetMemory()//
начальная установка памяти
{
      Mem[COUNT]=0; //
установка счетчика элементов массива
      Mem[NMAS]=50; //количество элементов в массиве
      Mem[SMAS]=0; //начальное значение суммы
      for(int i=0; i<50; i++) Mem[MAS+i]=i+1; //начальные значения для массива
}

void main()
{
      SimpleVirtualMachine(prog1,Mem); //
выполение первой программы
      SetMemory();
      SimpleVirtualMachine(prog2,Mem); //
выполение второй программы
      getch();
}
 

.: [предыдущая | оглавление | следующая] :.