Máquina virtual DIY

Às vezes, uma idéia vem à mente para se livrar da qual é muito difícil. Isso aconteceu comigo.

Decidi criar uma máquina virtual (VM), já que naquela época eu não tinha idéias, me pareceu que essa é uma ótima idéia. Se você estiver interessado, vá para o corte!

Teoria


Primeiro, um pouco de teoria. O que é uma máquina virtual em geral? Este é um programa ou conjunto de programas que permite emular algum tipo de plataforma de hardware, ou seja, um emulador de computador.

As próprias máquinas virtuais são diferentes, por exemplo, o Virtual Box é uma máquina virtual clássica que permite emular um computador real, mas, por exemplo, a JVM (Java virtual machine) não pode fazer isso.

Minha versão da VM será um pouco semelhante à JVM, simplesmente porque é um projeto de treinamento mais do que o objetivo de criar uma VM poderosa.

A memória


Então, agora vamos descobrir a memória. Para criar memória, decidi usar uma matriz int sem sinal. O tamanho da matriz é determinado usando uma macro. Na minha versão, o tamanho da memória é 4096 bytes (existem 1024 elementos na matriz e, como na maioria das plataformas, 4 bytes são alocados para dados int não assinados, 1024 * 4 = 4096), entre outras coisas, definiremos 8 registros por 8 células em cada uma já terão 256 bytes (8 * 8 * 4 = 256). É assim:

#define MEMSIZE 1024 unsigned int memory[MEMSIZE]; unsigned int reg[8][8]; 

Programação


Temos memória, mas agora como escrever código para nossa VM? Agora vamos lidar com esse problema, para começar, determinaremos os comandos que nossa máquina executará:

 enum commands { /*   / List of commands */ CRG = 1, /* Change ReGister -   [1] */ CRC, /* Change Register Cell [2] */ PRG, /* Put in ReGister -       [3] */ PRC /* Put Register Cell     [4] */ }; 

Cada equipe tem seu próprio sinalizador, definindo alguns parâmetros adicionais.
descreveremos sinalizadores:

 enum flags { /*   / List of flags */ STDI = 1, /*   / Standard flag */ STDA /*   / Address flag */ }; 

O comando padrão tem a forma: [command] [flag] [data] (a aparência de alguns comandos pode ser diferente), com base nisso, escreveremos um interpretador simples:

 if (memory[cell] == CRG && memory[cell + 1] == STDI) { indxX = memory[cell + 2]; cell++; } else if (memory[cell] == CRC && memory[cell + 1] == STDI) { indxY = memory[cell + 2]; cell++; } else if (memory[cell] == PRG && memory[cell + 1] == STDI) { reg[indxX][0] = memory[cell + 2]; cell++; } else if (memory[cell] == PRC && memory[cell + 1] == STDI) { reg[indxX][indxY] = memory[cell + 2]; cell++; } 

indxX e indxY são variáveis ​​que armazenam a posição atual do cursor no registro reg.
Célula é uma variável que armazena a posição atual do cursor na matriz de memória.

Mas programar com números não é muito conveniente, portanto, usando o pré-processador C, descreveremos nosso montador. Entendo que escrever asm com macros não é muito bom, mas esta solução é temporária.

Nosso código asm é assim:

 /*  */ #define $CRG {memory[memIndx++] = CRG;} #define $CRC {memory[memIndx++] = CRC;} #define $PRG {memory[memIndx++] = PRG;} #define $PRC {memory[memIndx++] = PRC;} /*  */ #define _$STDI {memory[memIndx++] = STDI;} #define _$STDA {memory[memIndx++] = STDA;} /*  */ #define _$DATA memory[memIndx++] = 

memIndx é uma variável que armazena a posição atual do cursor na matriz de memória.

E aqui está o código em nosso asm, colocando 123 no registro no endereço [1] [0] (primeiro registro, célula zero):

 $CRG /*   */ _$STDI /*   STDI */ _$DATA 1; /*   */ $CRC /*   */ _$STDI _$DATA 0; $PRC /*   */ _$STDI _$DATA 123; 

Parabéns, agora temos uma aparência de asm para o nosso carro!

Iniciar programas


Conseguimos que nossa máquina executasse programas, mas o código carece de portabilidade de uma máquina para outra, então agora criaremos um gerador de código de máquina da asm (e lembro que, diferentemente dos computadores reais, nossa máquina possui um código de máquina que não é apresentado em forma binária, e números decimais), em princípio, não é tão difícil, mas primeiro, vamos pensar na implementação.

Primeiro temos o código asm, agora precisamos convertê-lo em números e depois gravar o código da máquina resultante em um arquivo .ncp (programa de código numérico, na verdade, é um arquivo de texto, mas, para diferenciá-lo de todo o resto, criei minha própria extensão), depois disso precisamos executar o arquivo .ncp, é simples, já que o intérprete que escrevemos anteriormente reconhece os números; precisamos extrair apenas os dados do arquivo e transformá-los em números usando atoi ().

Vamos passar de palavras para ações:

Lendo o código e gravando-o em um arquivo:

 if (memory[i] == CRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRG, STDI); i++; } else if (memory[i] == CRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", CRC, STDI); i++; } else if (memory[i] == PRG && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRG, STDI); i++; } else if (memory[i] == PRC && memory[i + 1] == STDI) { fprintf(code, "%d %d ", PRC, STDI); i++; } 

O código faz parte do corpo da função ncpGen ().

Lendo um arquivo e sua execução:

 if (prog != NULL) { fread(txt, 1, len, prog); tok = strtok(txt, " "); while (tok != NULL) { memory[i] = atoi(tok); tok = strtok(NULL, " "); if (argc == 3 && strcmp(argv[2], "-m") == 0) { printf("%d\n", memory[i]); } i++; } memInter(); } else { perror("Fail"); } 

Agora, vamos definir uma macro para que, em vez de interpretar asm, o código se transforme em .ncp:

 #define _toNCP(name) {strcpy(filename, name);} {ncpGen();} 

Se alguma coisa, o artigo não apresenta todo o código, mas apenas uma pequena parte dele!

O código completo está no repositório do projeto.

Muito obrigado pela leitura!

Source: https://habr.com/ru/post/pt442988/


All Articles