virtuales una que paso maquinas maquina instalar instala gratis descargar como vm-implementation

vm-implementation - paso - que es una maquina virtual



¿Cómo voy a escribir una máquina virtual? (7)

¡Revisa lo que otros han hecho en esta área!

Una buena manera de recopilar información sobre un tipo particular de aplicación (y en su caso, también es una buena manera de recoger idiomas) es observar la estructura y el detalle de un proyecto de código abierto del mismo tipo. Uno puede decidir simplemente echar un vistazo, revisar brevemente y luego "olvidar", para comenzar su propio proyecto desde cero, pero en todos los casos este tipo de visita es beneficioso.

Como mencionó "arquitectura simple" y Zilog, pensé que el procesador Z80 podría ser una buena combinación. Por diversas razones, hay muchos proyectos actuales y pasados ​​en el género del emulador Z80. Por cierto, una de las razones es que muchas consolas de video de tipo tragamonedas antiguas se ejecutaban en Z80, lo que provocó que los jugadores nostálgicos escribieran emuladores para ejecutar sus viejos favoritos ;-)

Un ejemplo de un proyecto de este tipo es YAZE-AG que incluye tanto un emulador Z80 completo como C / PM . Todo está escrito en C. También es relativamente maduro (Versión 2.x) y activo. Supongo que este es el trabajo de un equipo muy pequeño (posiblemente de uno ;-)).

¡Buena suerte!

Estoy interesado en programar una máquina virtual, nada tan sofisticado como virtualbox o vmware, pero algo que puede emular una arquitectura simple, ya sea cisc o risc, como los modelos de arquitectura Zilog, SPARC, MIPS o 80686.

Supongo que al hacer esto sería relativamente simple hacer un emulador del mismo tipo, solo me interesa usar esto por experiencia más que cualquier otra cosa (siendo mi primer proyecto en C, prefiero hacer esto en C que en C Algo más).


Algo de la era de Zilog sería bueno porque probablemente pueda encontrar algún software que se ejecute en máquinas Z-80 reales y usarlo como prueba final.

El primer programa real que escribí (aparte de las asignaciones de clase de una página) fue un emulador para la minicomputadora HP2100A que había usado en la escuela secundaria. Escribí eso en B, el antecesor de C, y no creo que esto sea demasiado difícil para un primer programa de C. En todo caso, podría ser demasiado simple. Por supuesto, algo como el 80686 es mucho más desafiante que un Z-80, pero QEMU, VirtualBox y otros ya lo han hecho.

La parte más difícil de esto será todo el sistema de interrupción que conecta la máquina con el mundo externo.

Es posible que desee leer acerca de LLVM y decidir si realmente desea escribir una máquina virtual o un emulador.


Esto no es un aval de producto, sino una observación ...

Para empezar, tomaría un libro de Deitel y Deitel. (Probablemente este si quieres hacerlo en C) Parece que siempre tienen un capítulo sobre cómo crear una máquina virtual, junto con algunas instrucciones para escribir el código del ensamblador para tu máquina virtual, independientemente del idioma que enseñen.

Editar - Añadido

(aunque lo revisaría en una biblioteca antes de comprarlo en caso de que no esté entendiendo qué es lo que quieres escribir)


Si estás diseñando una CPU y emulandola,

tener el núcleo listo. Significado, escribir clases para registros. Escribe uno para las banderas. Escribe un controlador de memoria.

Piense en el tipo de opcodes. Además, ¿cuál es la longitud de las palabras? ¿Es una CPU de 16 bits? ¿8 bits?

¿Qué tipo de acceso de memoria desea utilizar? DMA? HDMA?

¿Qué tipo de interrupciones quieres apoyar? ¿Será la CPU una plataforma de aprendizaje? ¿Solo será una CPU y algo de memoria, o tendrá dispositivos conectados? (sonido, video, etc).

Aquí hay un código de mi emulador en el que estoy trabajando (dominio público). Llevo unos días trabajando en ello. Cerca de 3200 líneas de código hasta ahora (la mayoría de microcode.cs, que no se publica aquí debido a sus 2600 líneas de tamaño).

using System; namespace SYSTEM.cpu { // NOTE: Only level-trigger interrupts are planned right now // To implement: // - microcode // - execution unit // - etc // This is the "core"; think of the CPU core like a building. You have several departments; flags, memory and registers // Microcode is external class core { public cpu_flags flags; public cpu_registers registers; public cpu_memory memory; public core(byte[] ROM, byte[] PRG) { flags = new cpu_flags(); registers = new cpu_registers(); memory = new cpu_memory(ROM, PRG); return; } } } using System; namespace SYSTEM.cpu { class cpu_flags { // SYSTEM is not a 6502 emulator. The flags here, however, are exactly named as in 6502''s SR // They do NOT, however, WORK the same as in 6502. They are intended to similar uses, but the only identity is the naming. // I just like the 6502''s naming and whatnot. // This would otherwise be a register in SYSTEM.cpu_core.cpu_registers. SR, with the bits used correctly. // This would be less readable, code-wise, so I''ve opted to dedicate an entire CLASS to the status register // Though, I should implement here a function for putting the flags in a byte, so "SR" can be pushed when servicing interrupts public bool negative, // set if the high bit of the result of the last operation was 1 // bit 7, then so on overflow, // says whether the last arithmetic operation resulted in overflow (NOTE: No subtraction opcodes available in SYSTEM) // NO FLAG brk, // break flag, set when a BREAK instruction is executed // NO FLAG (would be decimal flag, but I don''t see why anyone would want BCD. If you want it, go implement it in my emulator; in software) // i.e. don''t implement it in SYSTEM; write it in SYSTEM ASM and run it in SYSTEM''s DEBUGGER irq, // whether or not an interrupt should begin at the next interrupt period (if false, no interrupt) zero, // says whether the last arithmetic operation resulted in zero carry; // set when alpha rolls from 0xFFFF to 0x0000, or when a 1 is rotated/shifted during arithmetic public cpu_flags() { negative = true; // all arithmetic registers are FFFF by default, so of course they are negative overflow = false; // obviously, because no arithmetic operation has been performed yet brk = false; irq = true; // interrupts are enabled by default of course zero = false; // obviously, since all arith regs are not zero by default carry = false; // obviously, since no carry operation was performed return; } // Explain: // These flags are public. No point putting much management on them here, since they are boolean // The opcodes that SYSTEM supports, will act on these flags. This is just here for code clarity/organisation } } using System; // This implements the memory controller // NOTE: NO BANK SWITCHING IMPLEMENTED, AND NOT PLANNED AT THE MOMENT, SO MAKE DO WITH TEH 64 // SYSTEM has a 16-bit address bus (and the maximum memory supported; 64K) // SYSTEM also has a 16-bit data bus; 8-bit operations are also performed here, they just use the low bits // 0x0000-0x00FF is stack // 0xF000-0xFFFF is mapped to BIOS ROM, and read-only; this is where BIOS is loaded on startup. // (meaning PROGRAM ROM can be up to 4096B, or 4K. Normally this will be used for loading a BIOS) // Mapping other PROGRAM ROM should start from 0x0100, but execution should start from 0xF000, where ROM/BIOS is mapped // NOTE: PROGRAM ROM IS 32K, and mapped from 0x0100 to 0x80FF // ;-) namespace SYSTEM.cpu { class cpu_memory { // to implement: // device interaction (certain addresses in ROM should be writeable by external device, connected to the controller) // anything else that comes to mind. // Oh, and bank switching, if feasible private byte[] RAM; // As in the bull? ... public cpu_memory(byte[] ROM, byte[] PRG) { // Some code here can be condensed, but for the interest of readability, it is optimized for readability. Not space. // Checking whether environment is sane... SYSTEM is grinning and holding a spatula. Guess not. if(ROM.Length > 4096) throw new Exception("****SYSINIT PANIC****: BIOS ROM size INCORRECT. MUST be within 4096 BYTES. STOP"); if (PRG.Length > 32768) throw new Exception("****SYSINIT PANIC**** PROGRAM ROM size INCORRECT. MUST be within 61184 BYTES. STOP"); if(ROM.Length != 4096) // Pads ROM to be 4096 bytes, if size is not exact { // This would not be done on a physical implementation of SYSTEM, but I feel like being kind to the lazy this.RAM = ROM; ROM = new byte[4096]; for(int i = 0x000; i < RAM.Length; i++) ROM[i] = this.RAM[i]; } if(PRG.Length != 32768) // Pads PRG to be 61184 bytes, if size is not exact { // again, being nice to lazy people.. this.RAM = PRG; PRG = new byte[32768]; for(int i = 0x000; i < RAM.Length; i++) PRG[i] = RAM[i]; } this.RAM = new byte[0x10000]; // 64K of memory, the max supported // Initialize all bytes in the stack, to 0xFF for (int i = 0; i < 0x100; i++) this.RAM[i] = 0xFF; // This is redundant, but desired, for my own undisclosed reasons. // LOAD PROGRAM ROM AND BIOS ROM INTO MEMORY for (int i = 0xf000; i < 0x10000; i++) // LOAD BIOS ROM INTO MEMORY { this.RAM[i] = ROM[i - 0xf000]; // yeah, pretty easy actually } // Remember, 0x0100-0x80FF is for PROGRAM ROM for (int i = 0x0100; i < 0x8100; i++) // LOAD PROGRAM ROM INTO MEMORY { this.RAM[i] = PRG[i - 0x100]; // not that you knew it would be much different } // The rest, 0x8100-0xEFFF, is reserved for now (the programmer can use it freely, as well as where PRG is loaded). // still read/writeable though return; } // READ/WRITE: // NOTE: SYSTEM''s cpu is LITTLE ENDIAN // WHEN DOUBLE-READING, THE BYTE-ORDER IS CONVERTED TO BIG ENDIAN // WHEN DOUBLE-WRITING, THE BYTE TO WRITE IS BIG ENDIAN, AND CONVERTED TO LITTLE ENDIAN // CPU HAS MAR/MBR, but the MEMORY CONTROLLER has ITS OWN REGISTERS for this? // SINGLE OPERATIONS public byte read_single(ref cpu_registers registers, ushort address) // READ A SINGLE BYTE { // reading from any memory location is allowed, so this is simple registers.memoryAddress = address; return registers.memoryBuffer8 = this.RAM[registers.memoryAddress]; } public ushort read_double(ref cpu_registers registers, ushort address) // READ TWO BYTES (converted to BIG ENDIAN byte order) { ushort ret = this.RAM[++address]; ret <<= 8; ret |= this.RAM[--address]; registers.memoryAddress = address; registers.memoryBuffer16 = ret; return registers.memoryBuffer16; } public void write_single(ref cpu_registers registers, ushort address, byte mbr_single) // WRITE A SINGLE BYTE { if (address < 0x0100) return; // block write to the stack (0x0000-0x00FF) if (address > 0xEFFF) return; // block writes to ROM area (0xF000-0xFFFF) registers.memoryAddress = address; registers.memoryBuffer8 = mbr_single; this.RAM[registers.memoryAddress] = registers.memoryBuffer8; return; } public void write_double(ref cpu_registers registers, ushort address, ushort mbr_double) // WRITE TWO BYTES (converted to LITTLE ENDIAN ORDER) { // writes to stack are blocked (0x0000-0x00FF) // writes to ROM are blocked (0xF000-0xFFFF) write_single(ref registers, ++address, (byte)(mbr_double >> 8)); write_single(ref registers, --address, (byte)(mbr_double & 0xff)); registers.memoryBuffer16 = mbr_double; return; } public byte pop_single(ref cpu_registers registers) // POP ONE BYTE OFF STACK { return read_single(ref registers, registers.stackPointer++); } public ushort pop_double(ref cpu_registers registers) // POP TWO BYTES OFF STACK { ushort tmp = registers.stackPointer++; ++registers.stackPointer; return read_double(ref registers, tmp); } // PUSH isn''t as easy, since we can''t use write_single() or write_double() // because those are for external writes and they block writes to the stack // external writes to the stack are possible of course, but // these are done here through push_single() and push_double() public void push_single(ref cpu_registers registers, byte VALUE) // PUSH ONE BYTE { registers.memoryAddress = --registers.stackPointer; registers.memoryBuffer8 = VALUE; this.RAM[registers.memoryAddress] = registers.memoryBuffer8; return; } public void push_double(ref cpu_registers registers, ushort VALUE) // PUSH TWO BYTES { this.RAM[--registers.stackPointer] = (byte)(VALUE >> 8); this.RAM[--registers.stackPointer] = (byte)(VALUE & 0xff); registers.memoryAddress = registers.stackPointer; registers.memoryBuffer16 = VALUE; return; } } } using System; namespace SYSTEM.cpu { // Contains the class for handling registers. Quite simple really. class cpu_registers { private byte sp, cop; // stack pointer, current opcode // private ushort pp, ip, // program pointer, interrupt pointer mar, mbr_hybrid; // memory address and memory buffer registers, // store address being operated on, store data being read/written // mbr is essentially the data bus; as said, it supports both 16 and 8 bit operation. // There are properties in this class for handling mbr in 16-bit or 8-bit capacity, accordingly // NOTE: Paged memory can be used, but this is handled by opcodes, otherwise the memory addressing // is absolute // NOTE: sp is also an address bus, but used on the stack (0x0000-0x00ff) only // when pushing to the stack, or pulling, mbr gets updated in 8-bit capacity // For pulling 16-bit word from stack, shifting register 8 left is needed, otherwise the next // POP operation will override the result of the last // Alpha is accumulator, the rest are general purpose public ushort alphaX, bravoX, charlieX, deltaX; public cpu_registers() { sp = 0xFF; // stack; push left, pop right // stack is from 0x0000-0x00ff in memory pp = 0xf000; // execution starts from 0xf000; ROM is loaded // from 0xf000-0xffff, so 4KB of ROM. // 0xf000-0xffff cannot be written to in software; though this disable // self-modifying code, effectively. ip = pp; // interrupt pointer starts from the same place as pp alphaX = bravoX = charlieX = deltaX = 0xffff; cop = 0x00; // whatever opcode 0x00 is, cop is that on init mar = mbr_hybrid = 0x0000; return; } // Registers: public ushort memoryAddress // no restrictions on read/write, but obviously it needs to be handled with care for this register { // This should ONLY be handled by the execution unit, when actually loading instructions from memory set { mar = value; } get { return mar; } } // NOTE: 8-bit and 16-bit address bus are shared, but address bus must have all bits written. // when writing 8-bit value, byte-signal gets split. Like how an audio/video splitter works. public byte memoryBuffer8 // treats address bus as 8-bit, load one byte { set { // byte is loaded into both low and high byte in mbr (i.e. it is split to create duplicates, for a 16-bit signal) mbr_hybrid &= 0x0000; mbr_hybrid |= (ushort)value; mbr_hybrid <<= 0x08; mbr_hybrid |= (ushort)value; } get { return (byte)mbr_hybrid; } } public ushort memoryBuffer16 // treats address bus as 16-bit, load two bytes { set { mbr_hybrid &= 0x0000; mbr_hybrid |= value; } get { return mbr_hybrid; } } public byte stackPointer // sp is writable, but only push/pull opcodes { // should be able to write to it. There SHOULD set { sp = value; } // be opcodes for reading from it get { return sp; } } public byte currentOpcode { set { cop = value; } get { return cop; } } public ushort programPointer // says where an instruction is being executed from { set { pp = value; } get { return pp; } } public ushort interruptPointer // says where the next requested interrupt should begin { // (copied into PP, after pushing relevant registers) set { ip = value; } get { return ip; } } public byte status(cpu_flags flags) // status word, containing all flags { byte ret = 0; if (flags.negative) ret |= 0x80; if (flags.overflow) ret |= 0x40; if (flags.brk) ret |= 0x10; if (flags.irq) ret |= 0x04; if (flags.zero) ret |= 0x02; if (flags.carry) ret |= 0x01; return ret; } } } using System; using System.Collections.Generic; namespace SYSTEM.cpu { class cpu_execution { public core processor; // the "core", detailing the CPU status, including memory, memory controller, etc public cpu_microcode microcode; // the microcode unit (note, microcode is plug and play, you could use something else here) public cpu_execution(byte[] ROM, byte[] PRG) // initialize execution unit and everything under it { processor = new core(ROM, PRG); microcode = new cpu_microcode(); return; } public void fetch() // fetch current instruction { processor.registers.currentOpcode = processor.memory.read_single(ref processor.registers, processor.registers.programPointer); return; } public void execute() // execute current instruction { processor = microcode.use(processor); return; } } }

microcode.cs, que emula el código de operación, no se incluye aquí porque tiene 2600 líneas de código.

Todo esto es C #.


Sugiero revisar el libro Elementos de sistemas de computación . Mientras avanza en el libro, construirá una computadora virtual a partir de compuertas lógicas básicas. Cuando haya terminado con el libro, tendrá un sistema operativo rudimentario, compilador, etc.
El código fuente, que está disponible en línea, también implementa la arquitectura de la computadora sobre Java.


Un ejercicio común es construir una calculadora sencilla. Solo tiene un número limitado de operaciones (generalmente 4, * / + - ), un tipo de datos (número) y probablemente tenga una muy buena comprensión de cómo debería funcionar. Eso hace que la depuración sea mucho más fácil.

A pesar de la simplicidad, ya tiene que lidiar con algunos problemas fundamentales de VM. Debe analizar una secuencia de comandos, almacenar múltiples objetos en los que está trabajando y lidiar con la salida.

Casualmente, los IC de calculadora son los precursores de las CPU, por lo que este enfoque también tiene sentido desde una perspectiva histórica.


Unos pocos pensamientos:

  • Los conjuntos de instrucciones anteriores serán más simples, por lo que podrían ser un buen lugar para comenzar.
  • Elija una arquitectura risc: descodificar el flujo de instrucciones será mucho más fácil.
  • Ignorar cosas como interrupciones, NMIs, etc.
  • Siempre hay muchos detalles complicados al emular con precisión el arranque. En su lugar, elija algo muy simple como iniciar la ejecución en la dirección cero, con todos los registros configurados en cero.
  • Los programas reales también necesitarán cosas como la emulación de hardware real, así que no hagas eso.
  • Es posible que desee ampliar el conjunto de instrucciones con unas pocas instrucciones especiales de E / S para leer un número, escribir un carácter (o incluso una cadena), etc., de modo que pueda escribir programas de prueba simples que realmente realicen la I / O muy simple.
  • Analizar un formato de archivo de objeto como elf puede ser una gran cantidad de trabajo por sí mismo. Con herramientas como objdump, probablemente pueda extraer solo la sección de texto (es decir, las instrucciones) como binario (al menos ascii hex).
  • Comience por escribir un desensamblador para cualquier conjunto de instrucciones que desee emular, o al menos para el subconjunto inicial que disparará. Lo necesitarás para la depuración de todos modos.
  • Averigüe cómo hacer funcionar el conjunto de instrucciones elegido (gnu assembler) para que pueda producir archivos de objetos y programas de prueba conocidos.

A menos que tenga conocimiento de otros lenguajes de programación y / o una comprensión razonable del ensamblador, este es un primer proyecto de C bastante desafiante. ¡Nunca menos, buena suerte!