Bei der Entwicklung verschiedener Geräte tritt häufig ein Problem auf: Der Algorithmus von Gerät zu Gerät wird stellenweise wiederholt, und die Geräte selbst sind völlig unterschiedlich. Ich habe drei Geräte in der Entwicklung, die an einigen Stellen die Funktionalität voneinander wiederholen. Sie verwenden drei verschiedene Prozessoren (drei verschiedene Architekturen), aber es gibt nur einen Algorithmus. Um alles irgendwie zu vereinheitlichen, war geplant, eine minimale virtuelle Maschine zu schreiben.
Im Allgemeinen habe ich nach dem Bytecode von Java, Lua und anderen Computern gesucht, aber ich wollte nicht wirklich das gesamte verfügbare Gepäck in eine andere Sprache umschreiben. Also haben wir uns für die Sprache entschieden - C. Obwohl Java oder Lua immer noch attraktiv klingt. [1] [2] [3] [4].Das nächste Kriterium war der Compiler. In meinen Projekten verwende ich am häufigsten "von Studenten für Cookies geschrieben GCC (c) anonymus". Jene. Wenn Sie Ihre Architektur beschreiben, müssten Sie sich die ganze Reihe von GCC (Compiler, Linker usw.) einfallen lassen.Da ich faul bin, suchte ich nach der kleinstmöglichen Architektur mit GCC-Unterstützung. Und es wurde der MSP430.Kurzbeschreibung
MSP430 ist eine sehr einfache Architektur. Es hat nur 27 Anweisungen [5] und fast jede Adressierung.Der Aufbau der virtuellen Maschine begann mit dem Kontext des Prozessors. Der Kontext des Prozessors in Betriebssystemen ist eine Struktur, die den Status des Prozessors vollständig beschreibt. Der Status dieses virtuellen Prozessors wird wie folgt beschrieben:- Aktuelles Team
- Register
- Optionaler Status der Interrupt-Register
- Optionaler Inhalt von RAM und ROM
Die Register des MSP430 sind 16. Von den 16 Registern werden die ersten 4 als Systemregister verwendet. Angenommen, ein Nullregister ist für den aktuellen Zeiger auf den Befehl verantwortlich, der aus dem Adressraum (Befehlszähler) ausgeführt wird.Weitere Informationen zu Registern finden Sie im Original-Benutzerhandbuch msp430x1xxx [6]. Neben den Registern gibt es auch den Inhalt des Adressraums - RAM, ROM. Da es jedoch einfach ist, die „Host-Maschinen“ (die Maschine, die den Code der virtuellen Maschine ausführt) im Speicher der virtuellen Maschine zu halten, macht dies häufig keinen Sinn - es wird ein Rückruf verwendet.Mit dieser Lösung können Sie "vollständig linkshändige" Programme auf Prozessoren mit Harvard-Architektur ausführen (siehe AVR [7] [8]) und das Programm aus externen Quellen (z. B. i2c-Speicher oder einer SD-Karte) entnehmen.Ebenfalls im Kontext des Prozessors ist eine Beschreibung von Interrupt-Registern (SFRs). Das MSP430-Interrupt-System ist am genauesten in [6], Abschnitt 2.2 beschrieben.Aber in der beschriebenen virtuellen Maschine habe ich mich etwas vom Original entfernt. Im ursprünglichen Prozessor befinden sich die Interrupt-Flags in den Peripherieregistern. In diesem Fall werden Interrupts in SFR-Registern beschrieben.Die Prozessorperipherie wird auf die gleiche Weise per Rückruf beschrieben, sodass Sie nach Belieben Ihre eigenen Peripheriegeräte erstellen können.Das nächste Prozessorelement ist der Befehlsmultiplexer. Der Befehlsmultiplexer führt eine separate Funktion aus. Der Multiplexer wählt den Befehl selbst aus dem Befehlswort aus, adressiert die Quelle und den Empfänger und führt die Aktion des ausgewählten Befehls aus.Separate Funktionen beschreiben die Quelladressierung (SRC) und den Empfänger.Wie man es benutzt
Im Beispielordner aus dem Projekt-Repository [9] finden Sie Beispiele für die folgenden Prozessoren:- STM8 für den IAR-Compiler
- STM8 für SDCC-Compiler
- STM32 für Keil Armcc Compiler
- AVR für GCC Compiler
In der Datei Cpu.h ist der Prozessor konfiguriert.Beschreibung der Einstellungen unten:- RAM_USE_CALLBACKS - Gibt an, ob Aufrufe (Rückrufe) anstelle einzelner Arrays im Kontext des Prozessors verwendet werden sollen. Gibt an, ob Aufrufe für die Arbeit mit RAM verwendet werden sollen (Aufrufe cpu.ram_read, cpu.ram_write).
- ROM_USE_CALLBACKS - Gibt an, ob Aufrufe für die Arbeit mit ROM verwendet werden sollen (Aufruf von cpu.rom_read).
- IO_USE_CALLBACKS - Ob Aufrufe für die Arbeit mit der Peripherie verwendet werden sollen (Aufrufe cpu.io_read, cpu.io_write). Wenn 0, sollten die Funktionen für die Arbeit mit der Peripherie in der Funktion msp430_io aus der Datei cpu.c beschrieben werden
- RAM_SIZE - RAM-Größe (RAM), die Endadresse wird basierend auf diesem Parameter automatisch neu berechnet
- ROM_SIZE - ROM-Größe (ROM), die Startadresse wird basierend auf diesem Parameter automatisch neu berechnet
- IRQ_USE - Gibt an, ob Interrupts verwendet werden. Wenn 1, werden Interrupts aktiviert
- HOST_ENDIANESS - Gibt die Bytereihenfolge des Host-Controllers an (des Controllers, auf dem die virtuelle Maschine ausgeführt wird). Die Architekturen AVR, X86, STM32 sind Little-Endian, STM8 sind Big-Endian
- DEBUG_ON - Gibt an, ob das Debuggen verwendet wird. Das Debuggen erfolgt über fprintf - stderr
Die Verwendung der Bibliothek beginnt mit der Verbindung von cpu.c und cpu.h mit dem Projekt.#include "cpu.h"
Als nächstes folgt die Ankündigung des Prozessorkontexts. Abhängig von der Verwendung der Parameter * _USE_CALLBACKS ändert sich der Kontextdeklarationscode.für alle * _USE_CALLBACKS = 1 Prozessorkontextdeklarationen sehen folgendermaßen aus:msp430_context_t cpu_context =
{
.ram_read_cb = ram_read,
.ram_write_cb = ram_write,
.rom_read_cb = rom_read,
.io_read_cb = io_read,
.io_write_cb = io_write
};
Wobei * _cb-Variablen Funktionszeiger akzeptieren (siehe Beispiele).Umgekehrt sehen die Deklarationen für * _USE_CALLBACKS = 0 folgendermaßen aus:msp430_context_t cpu_context =
{
.rom = { },
};
Als nächstes folgt die Kontextinitialisierung über die Funktion:msp430_init(&cpu_context)
Und jeweils einen Befehl über eine Funktion ausführen:while(1)
msp430_cpu(&cpu_context)
Rückrufe für die Arbeit mit dem Adressraum sehen folgendermaßen aus:uint16_t io_read(uint16_t address);
void io_write(uint16_t address,uint16_t data);
uint8_t ram_read(uint16_t address);
void ram_write(uint16_t address,uint8_t data);
uint8_t rom_read(uint16_t address);
Adressen für E / A werden relativ zum Adressraum 0 übertragen (d. H. Wenn das Programm der virtuellen Maschine auf P1IN zugreift, das der Adresse 0x20 zugewiesen ist, wird die Adresse 0x20 an die Funktion übergeben).Im Gegensatz dazu werden die Adressen für RAM und ROM relativ zu den Startpunkten übertragen (wenn beispielsweise auf die Adresse 0xfc06 zugegriffen und das ROM bei 0xfc00 gestartet wird, wird die Adresse 0x0006 an die Funktion übergeben. Das heißt, die Adresse ist von 0 bis RAM_SIZE, 0 - ROM_SIZE).Dies ermöglicht die Verwendung von externem Speicher Zum Beispiel I2C (das den Prozessor bereits verlangsamt).Wie zu vervollständigen
Das Projekt ist noch nicht abgeschlossen. Es funktioniert, Test-Firmware funktioniert mit einem Knall. Die meisten Compiler verwenden jedoch praktisch keine unterschiedlichen spezifischen Befehle (z. B. Dadd ist die dezimale Addition von Quelle und Empfänger (mit Silbentrennung)). Es besteht also keine Notwendigkeit, über 100% ige Kompatibilität mit echten Prozessoren zu sprechen.Natürlich gibt es ungefähr zwei Dutzend Host-Maschinenoperationen pro Befehl einer virtuellen Maschine, daher ist es nicht sinnvoll, über Geschwindigkeitsmerkmale zu sprechen.Projektquellen und eine ausführlichere Beschreibung finden Sie unter bitbucket.org [9].Ich würde mich freuen, wenn dieses Projekt für jemanden nützlich ist.[1] dmitry.gr/index.php?r=05.Projects&proj=12.%20uJ%20-%20a%20micro%20JVM[2] www.harbaum.org/till/nanovm/index.shtml[3]www.eluaproject.net[4] code.google.com/p/picoc[5] de.wikipedia.org/wiki/MSP430[6] www.ti.com/lit/ug/slau049f/slau049f.pdf[7] en.wikipedia.org/wiki/%D0%93%D0%B0%D1%80%D0%B2%D0%B0%D1%80%D0%B4%D1%81%D0%BA%D0%B0%D1 % 8F_% D0% B0% D1% 80% D1% 85% D0% B8% D1% 82% D0% B5% D0% BA% D1% 82% D1% 83% D1% 80% D0% B0[8] de .wikipedia.org / wiki / AVR[9] bitbucket.org/intl/msp430_vm