Verstehen Sie die virtuelle Maschine der Programmiersprache in einem Artikel

Verstehen Sie die virtuelle Maschine der Programmiersprache in einem Artikel

Einführung in virtuelle Maschinen

Virtuelle Maschine ist, wie der Name schon sagt, eine virtuelle Maschine. Eine detailliertere Erklärung ist, dass Software verwendet, um Hardwareressourcen (d. h. Hardwarefunktionen) zu simulieren. Die virtuelle Maschine ist eigentlich nur ein gewöhnlicher Prozess, aber die Funktion dieses Prozesses besteht darin, die Ressourcen der Hardware zu simulieren.

Es gibt zwei Hauptkategorien von virtuellen Maschinen. Eine davon sind unsere gängigen virtuellen Maschinen wie VMware und VirtualBox. Die Hauptfunktion besteht darin, verschiedene Betriebssysteme darauf zu installieren.

Eines ist das Thema, über das wir heute sprechen werden:Virtuelle Maschine in Programmiersprache. Die Hauptfunktion besteht darin, die Funktion der Hardware-CPU zu simulieren und den interpretierten Zwischencode auszuführen – Bytecode. Zum Beispiel sprechen wir oft über Java Virtual MachineJVM und Ruby ​​Virtual MachineYARV< a i =8>Warten.

Text

Was hat die virtuelle Maschine gemacht?

Beginnen wir ohne weitere Umschweife jetzt. Der Einfachheit halber beziehen sich die unten erwähnten virtuellen Maschinen speziell auf virtuelle Maschinen in Programmiersprache.

Was sind die drei Hauptaufgaben der CPU?

Wie wir gerade gesagt haben, besteht die Hauptfunktion der virtuellen Maschine darin, die Funktionen der CPU durch Software zu simulieren. Um zu verstehen, was die virtuelle Maschine tut, müssen wir daher zunächst verstehen, was die CPU hauptsächlich tut!

Tatsächlich besteht die Hauptaufgabe der CPU darin, drei Dinge zu tun:1. Anweisungen abrufen, 2. Dekodieren, 3. Ausführen

1. Finger holen

Abrufen bedeutetAbrufen des nächsten auszuführenden Befehls vom Programmzähler (PC), bei dem es sich um eine binäre Codierung handelt.

Hier ist eine Erklärung des Programmzählers (PC). Der Programmzähler wird auch PC oder Programmzähler genannt und wird verwendet, um auf die Adresse des nächsten auszuführenden Befehls zu zeigen. Da die CPU jeweils nur eine Anweisung ausführt, entspricht dies nach der Ausführung mehrerer Anweisungen dem Zählen der Anweisungen im Programm und wird daher als Programmzähler bezeichnet. Der Programmzähler ist nur ein Konzept, und jede CPU hat ihre eigene Implementierung. Beispielsweise heißt das Register in der ARM-System-CPU, das die Adresse des nächsten Befehls speichert, PC, während das Register in der X86-System-CPU, das den nächsten speichert, PC genannt wird Die Anweisung heißt IP oder Instruction Pointer.

2.Dekodierung

Dekodierung ist auch Dekodierung, was bedeutet, dassdie Kodierung der Anweisung analysiert und der Operationscode und der Operand in der Anweisung bestimmt werden. Die Anweisung besteht übrigens ausOpcode und Operand.

3.Ausführung

Ausführung bedeutetCPU verwendet Aktionen, um die Absicht der Anweisung zu demonstrieren. Eine Anweisung ist eine Art ideologische Absicht, und die ideologische Absicht erfordert, dass Maßnahmen umgesetzt werden, um umgesetzt zu werden.

Was die virtuelle Maschine tut

Wir haben gesagt, dass virtuelle Maschinen Software verwenden, um CPU-Funktionen zu simulieren. Mit anderen Worten, damit eine virtuelle Maschine die Funktionen einer CPU erfolgreich simulieren kann, muss sie tatsächlich drei Hauptaufgaben erledigen, die die CPU tut: < /a >. Abrufen, dekodieren und ausführen

1. Finger holen

Während der Befehlsabrufphase ruft die CPU binäre Befehle aus dem Speicher ab. Der binäre Befehlsstrom ist das Abbild des Programms, das vom Programmlader des Systems in den Speicher geladen wird. Die CPU versteht binäre Anweisungen direkt. Sie weiß, wie viel Speicher eine Anweisung benötigt, wie viele Operanden sie benötigt und was diese Anweisung bewirkt, da die CPU automatisch den Opcode und die Operanden aus der Anweisung erhält und auch weiß, wie lange sie dauert Binärstrom ist der Opcode, wie viele Operanden dieser Opcode benötigt und wie lang und verschoben die Operanden im Befehlsstrom sind. Mit anderen Worten: Anweisungen sind direkt für die CPU bestimmt und die CPU versteht das Format dieses Befehlsstroms.

Mit anderen Worten, die virtuelle Maschine muss tatsächlich die CPU-Wertoperation einbeziehen. Was erhält die CPU also?

Da es sich bei der virtuellen Maschine nur um ein Programm handelt, handelt es sich nach dem Laden in den Speicher um einen laufenden Prozess. Daher sind die vom laufenden Prozess der virtuellen Maschine zu lesenden Codeanweisungen definitiv keine von der CPU erkannten Binärströme . Natürlich kann der Quellcode letztendlich auch in einen Binärstream analysiert werden, aber die Entwicklungskosten für die Verwendung einer virtuellen Maschine zum Parsen des Binärstreams sind sehr hoch. Daher sind die während der Befehlsabrufphase der virtuellen Maschine gelesenen Anweisungen tatsächlich das, was wir oft als Bytecode bezeichnen.

Bytecode

Was ist Bytecode? Um Bytecode zu verstehen, müssen Sie zunächst den Kompilierungsprozess des Quellcodes der Programmiersprache verstehen.

Wir sprechen hier hauptsächlich über den Kompilierungsprozess interpretierter Sprachen. Beim Quellcode handelt es sich lediglich um Textinformationen, die hauptsächlich die folgenden Phasen durchlaufen (hier werden nur die ersten paar Phasen erwähnt):

  • lexikalische Analyse

    Bei der lexikalischen Analyse werden Quellcodetextinformationen einzeln in Token analysiert. Zum Beispiel:

    int a = 10;
    

    Nach der lexikalischen Analyse werden hier 5 Token analysiert: „int“, „a“, „=“, „10“, „;“

    Entspricht: Schlüsselwörtern, IDs, Zuweisungsoperatoren, Zahlen, Semikolons

    Das Ausgabetoken der lexikalischen Analyse wird als Eingabe für die Syntaxanalyse verwendet.

  • Syntaktische Analyse und semantische Analyse

    Die Ergebnisse der Syntaxanalyse und der semantischen Analyse identifizieren und analysieren die Syntax und Semantik des Satzes und generieren einen Syntaxanalysebaum (AST). In dieser Phase können Sie feststellen, ob Fehler in der Syntax vorliegen, und die Semantik verstehen jedes Codesatzes.

  • Zwischencode generieren

    Am Ende der semantischen Analyse generiert Zwischencode, bei dem es sich um Bytecode handelt

Der Bytecode wird hier als Anweisung an die virtuelle Maschine eingegeben. Mit anderen Worten, der Bytecode hier entspricht tatsächlich dem binären Befehlsstrom der CPU, aber der Bytecode ist kein binärer Strom, sondern nur ein Textstrom, aber er hat zu diesem Zeitpunkt bereits die Funktion von Anweisungen, und Sie können dies tun Verstehen Sie es als Assembler-Code (aber es ist kein Assembler-Code und unterscheidet sich dennoch vom Assembler-Code).

Bytecode, auch Bytecode genannt, ist ein Zwischencode, der häufig in Sprachen für virtuelle Maschinen wie PHP, Python usw. verwendet wird. Bytecode ist die Ausgabe der semantischen Analyse und die Eingabe der virtuellen Maschine. Das heißt, der Zielcode der semantischen Analyse ist Bytecode. Der Bytecode ist nur an eine bestimmte virtuelle Maschine gebunden, dh er gilt nur für eine bestimmte virtuelle Maschine und ist nicht an eine bestimmte CPU-Hardware gebunden. Daher ist der Bytecode angepasst und die Form ist nicht eingeschränkt. Anweisungen bestehen aus Operationscodes und Operanden. Der Operationscode heißt Opcode und der Operand heißt Operand. Um die Effizienz der virtuellen Maschine zu verbessern, sollten die Opcode-Typen so klein wie möglich gehalten werden. sind grundsätzlich kleiner als 256, sodass der Opcode durch 1 Byte dargestellt werden kann im BytecodeByte , das ist auch der Grund für das Wort

Der von der CPU zum Abrufen von Anweisungen verwendete Befehlsstrom ist ein Binärstrom, und der von der virtuellen Maschine zum Abrufen von Werten verwendete Befehlsstrom ist ein Bytecode-Strom. Der Bytecode wird als Eingabe für die virtuelle Maschine verwendet, und die virtuelle Maschine analysiert den Eingabebytecode und führt die Absicht des Bytecodes aus.

2.Dekodierung
  • Bestimmen Sie die Art der Anweisung und geben Sie den entsprechenden Verarbeitungsablauf ein
3.Ausführung
  • Führen Sie entsprechend der Absicht der Anweisung die entsprechende Prozessfunktion aus
Klassifizierung virtueller Maschinen

Nachdem die CPU die oben genannten drei Schritte abgeschlossen hat und das Ergebnis ausgeben möchte, speichert sie das Ausgabeergebnis in einem Register oder Speicher. In den meisten Fällen wird es jedoch aufgrund der Zugriffsleistung des Registers direkt im Register gespeichert viel höher als die des Speichers. Zugriffsleistung. Wir wissen jedoch bereits, dass es sich bei der virtuellen Maschine nur um einen laufenden Prozess handelt. Wenn die Ergebnisse nach Abschluss der oben genannten drei Schritte gespeichert werden sollen, können sie nicht direkt im Register gespeichert werden, sondern können nur im Register gespeichert werden Speicher, weil die virtuelle Maschine Die Maschine selbst ist nur ein Programm im Speicher.

Virtuelle Maschinen können entsprechend der Speicherstruktur der Ausgabeergebnisse in zwei Typen unterteilt werden: Registerbasierte virtuelle Maschinen und . Stackbasierte virtuelle Maschine

Registerbasierte virtuelle Maschine
  • Implementierung – Um es ganz klar auszudrücken: simuliert vollständig die Funktion des Registers innerhalb der virtuellen Maschine und implementiert das Register innerhalb der virtuellen Maschine< /span>

In diesem Fall wird normalerweise ein -Array verwendet, um eine Registerstruktur zu implementieren. Jedes der Arrays Die Elemente sind Register.

Anweisungen bestehen aus Opcodes und Operanden. Die Operanden können in simulierten Registern abgelegt werden, und die Ergebnisse der Befehlsausführung können auch in simulierten Registern gespeichert werden.

Der Vertreter dieser Art von virtueller Maschine ist: lua

  • Nachteile – Schwierig zu implementieren

    Anweisungen bestehen aus Opcodes und Operanden. Die Operanden der beiden Arten virtueller Maschinen werden in unterschiedlichen Strukturen gespeichert, sodass die Schwierigkeit beim Generieren von Anweisungen unterschiedlich ist.

    Die von der registerbasierten virtuellen Maschine simulierten Register sind die gleichen wie die des realen Systems, da die Anzahl der Register begrenzt ist und begrenzten Registern unbegrenzte Variablen zugewiesen werden Vermeiden Sie Konflikte, bei denen es um Registerzuordnungsalgorithmen geht, wie z. B. klassische Algorithmen zum Färben von Diagrammen, sodass es schwieriger wird.

Stapelbasierte virtuelle Maschine
  • Implementierung – Register werden nicht simuliert. Das Ausführungsergebnis der Anweisung wird im simulierten Laufzeitstapel gespeichert. Die Operanden können im Laufzeitstapel oder direkt im Laufzeitstapel gespeichert werden . unter

    Der Stapel selbst ist eine Last-In-First-Out-Datenstruktur. Die Ausführungsergebnisse werden im Allgemeinen in Form eines Pushs an die Spitze des Stapels verschoben. Um das Stapelgleichgewicht aufrechtzuerhalten, müssen sie sofort herausgenommen werden Das heißt, die Ergebnisse werden durch die Pop-Operation erhalten. Der hier erwähnte „simulierte Laufzeitstack“ ist nicht nur in stapelbasierten virtuellen Maschinen verfügbar. Registerbasierte virtuelle Maschinen simulieren auch Laufzeitstacks, da dies zur Ausführung von Funktionen oder Methoden erforderlich ist. Stapel werden im Allgemeinen mit linearen Strukturen wie Arrays und verknüpften Listen simuliert.

    Diese Art von virtueller Maschine repräsentiert: jvm

  • Nachteile – Die Leistung stapelbasierter virtueller Maschinen ist etwas geringer

    Zuerst Der Stapel wird durch die Speicherstruktur simuliert. Das Schieben und Öffnen des Stapels ist in zwei Schritte unterteilt. Bewegen Sie zuerst den oberen Zeiger des Stapels und erhalten Sie dann die Daten. Wenn Sie die Daten übergeben, müssen Sie den Operanden auf den Stapel schieben und ihn dann vom Empfänger vom Stapel entfernen, alsonoch ein Vorgang

    Zweitens gibt es Raum für Optimierung, wenn die Operanden in Registern platziert werden. Wenn sie auf dem Stapel platziert werden, kann die Position nicht verschoben werden, normalerweise die Oberseite des Stapels und die Unterseite des Stapels (befindet sich oberhalb der Oberseite des Stapels, an zweiter Stelle nach dem verwendeten Steckplatz oben auf dem Stapel), also das Gesamtoptimierungspotenzial ist etwas niedriger

Warum virtuelle Maschinen verwenden?

Wie wir alle wissen, werden virtuelle Maschinen hauptsächlich für interpretierte Sprachen wie PHP, Perl, Python, Java usw. verwendet. Für kompilierte Sprachen ist keine virtuelle Maschine erforderlich, da kompilierte Sprachen Code direkt in Binärdateien generieren, sodass sie direkt auf der Hardware-CPU ausgeführt werden können.

Tatsächlich sind virtuelle Maschinen langsamer als echte physische Maschinen.

Der Grund, warum eine virtuelle Maschine langsamer ist als eine physische Maschine, liegt darin, dass die Software der virtuellen Maschine unterschiedliche Objekte bedient, das heißt, ihre Rollen sind unterschiedlich. Ihre Kunden erkennen nie, dass es sich um eine virtuelle Maschine handelt, und behandeln sie immer wie eine echte physische Maschine Maschine. Warum sind virtuelle Maschinen im Detail langsam?

Das Programm wird schließlich in Anweisungen kompiliert. Die Operanden in fast jeder Anweisung umfassen Register. Die in der virtuellen Maschine simulierten Register sind eine Art Datenstruktur (z. B. ein Array, die Form ist jedoch nicht beschränkt). Diese Datenstruktur muss sich im Speicher befinden. Speicheroperationen sind mehrere Größenordnungen langsamer als echte Registeroperationen. Die CPU bevorzugt die Verwendung von Registern, da sich die Register innerhalb der CPU befinden und mit der CPU-Geschwindigkeit konsistent sind. < a i=1>Eine Operation Es ist nur ein Taktzyklus erforderlich. Der Speicher ist extern und Speicheroperationen müssen über den Bus erfolgen. Es dauert Dutzende von Taktzyklen, bis der Speicher die Lese- und Schreibnachrichten empfängt. Am meisten Das Schlimmste ist, dass die Zeit, die für das Lesen und Schreiben des Speichers aufgewendet wird, für die CPU astronomisch ist. Wenn die Daten abgerufen werden, ist die CPU alt . Um die simulierten Register aufrechtzuerhalten, müssen in der virtuellen Maschine einige zusätzliche verwandte Wartungsanweisungen geschrieben werden, z. B. das Zählen der Register, um die Häufigkeit der Verwendung der Register zu bestimmen, die als Grundlage für die Beurteilung des Zeitpunkts verwendet werden kann Nächstes Mal Register zuweisen. Was noch ärgerlicher ist, ist, dass man, wenn alle Register verwendet werden, zuerst darüber nachdenken muss, eines zu ersetzen, um Platz für die Ausführung dieser Anweisung zu schaffen usw. Dies erfordert eine Reihe von Algorithmen, und der Algorithmus selbst befindet sich im Interpreter ( Vielleicht in Im Compiler, möglicherweise in der virtuellen Maschine, ist die Anzahl der Anweisungen exponentiell, wenn es um die physische CPU geht. Dadurch entsteht eine Menge unerträglicher Redundanz für einfache Lese- und Schreibvorgänge für Register, sodass das Lesen und Schreiben eines Registers in der virtuellen Maschine mehr erfordert und kompliziertere Speicheroperationen, während die Registeroperation der physischen Maschine nur ein direkter Schritt ist, der zwangsläufig viel schneller ist als die virtuelle Maschine. Es ist unvermeidlich, dass fast jede Anweisung Register enthält und die virtuelle Maschine eine solche Verzögerung bei der Ausführung jeder Anweisung hat. Dies ist ein Problem für Hunderttausende von Anweisungen. In Bezug auf das Programm ist der Geschwindigkeitsunterschied offensichtlich. Darüber hinaus ist es viel, wenn einige Verarbeitungsalgorithmen in der virtuellen Maschine aufgerufen werden, also in der LaufzeitphaseAn die Umstände anpassen und sofort handeln einfacher als in der Kompilierungsphase. Bei binären ausführbaren Programmen, die diese Probleme lösen, wird die Effizienz weiter verringert.

Warum verwenden viele Sprachen immer noch virtuelle Maschinen, da virtuelle Maschinen langsamer sind als physische Maschinen?

Es gibt hauptsächlich folgende Gründe:

  • 1. Starke Tragbarkeit

    Der vom Interpreter generierte Zwischencode (d. h. Bytecode) wird nicht nur als Eingabe für die virtuelle Maschine verwendet und simuliert den binären Befehlsstrom, sondern auch aus einem wichtigeren Grund:Dieser Satz von Zwischencodes kann von einer virtuellen Maschine analysiert und ausgeführt werden. Daher kann dieser Satz von Zwischencodes plattformübergreifende Funktionen erreichen, solange jedes Betriebssystem über diese virtuelle Maschine verfügt.

    Wirklich geschafftEinmal schreiben, überall ausführen

  • 2. In den meisten Fällen ist die virtuelle Maschine nicht der eigentliche Flaschenhals für die Langsamkeit des gesamten Systems.

    Die virtuelle Maschine bedient Skriptsprachen. Bei der Ausführung von Skriptsprachen mangelt es zwar an Geschwindigkeit, das ist aber nicht von Bedeutung.

    Schließlich handelt es sich bei der Ausführung der Skriptsprache durch eine virtuelle Maschine um einen speicherinternen Vorgang. Es gibt andere Vorgänge, die langsamer und sogar viel langsamer sind, wie z. B. Festplattenvorgänge und Netzwerkübertragungen, die im System unvermeidlich sind. Ob es ist eine kompilierte Sprache oder eine Skriptsprache, die solche Vorgänge ausführt. Sie wird jedes Mal vom Betriebssystem blockiert, und die Zeit dieses Prozesses ist um mehrere Größenordnungen langsamer als die der virtuellen Maschine, die das Bytecode-Anweisungsprogramm ausführt Die Skriptsprache ist immer noch relativ schnell.

  • 3. Einfachere Entwicklung leistungsstarker Skriptsprachen

    Skriptsprachen haben eine einfachere Syntax als kompilierte Sprachen und können mit kürzeren Codes mehr Funktionen implementieren. Dies ist für Entwickler einfacher und bequemer zu verwenden. Die Hauptgründe sind folgende:

    • Die meisten virtuellen Maschinen werden mit Hochsprachen entwickelt. Hochsprachen sind leistungsfähiger und es stehen viele vorgefertigte Softwarepakete zur Verfügung. Beispielsweise können „Hallo,“ + „Welt“ in einer Skriptsprache zwei Zeichenfolgen zu „Hallo, Welt“ verketten. Um diese Funktion in einer Skriptsprache zu erreichen, kann die dahinter stehende Hochsprache (z. B. die C-Sprache) verwendet werden Rufen Sie die Funktion strcat. auf, um zwei Zeichenfolgen direkt zu verbinden. Die Verwendung von Low-Level-Assembly wäre viel schwieriger, sodass in Hochsprachen geschriebene Parser (einschließlich lexikalischer Analysatoren, Syntax- und Semantikanalysatoren sowie virtuelle Maschinen) problemlos erstellt werden können leistungsstarke Skriptsprache
    • Die virtuelle Maschine führt nur den Opcode aus. Ein Opcode wird häufig durch mehrere Funktionen auf der Ebene der virtuellen Maschine implementiert, was bedeutet, dass die Funktion eines Opcodes sehr leistungsfähig ist und der Opcode durch eine Skriptsprache generiert wird, also die Skriptsprache unvermeidlich, dass die Funktion sehr mächtig ist
    • Die drei Schritte, in denen die CPU Anweisungen ausführt, sind Abrufen, Dekodieren und Ausführen. Die ersten beiden Schritte sind sehr zeitaufwändig. Diese drei Schritte gibt es auch in der virtuellen Maschine. Ebenso sind das Abrufen und Dekodieren von Anweisungen in diesen drei Schritten die zeitaufwändigsten Teile der virtuellen Maschine. Im Gegensatz zur CPU , die virtuelle Maschine kann diese zeitaufwändigen Teile vermeiden. Sie stellt solche Anweisungen nicht bereit. Sie wird direkt von der Front-End-Syntax- und Semantikanalyse verarbeitet, ohne die virtuelle Maschine zu durchlaufen. Beispielsweise für den Ausdruck 1+2 die Die CPU muss dazu die Anweisung add verwenden. Dies beinhaltet „Abrufen, Dekodieren und Ausführen“. Die virtuelle Maschine benötigt jedoch nicht den Add-Opcode (natürlich kann sie ihn je nach Implementierung auch haben) und Der Ausdruck der Entwicklungssprache kann direkt in der Phase der semantischen Analyse generiert werden. „1+2-Fliegenvermeidung“ Anstelle von „Abrufen und Dekodieren“ entspricht dies dem direkten „Ausführen“.

Zusammenfassend lässt sich sagen, dass die Wahl der virtuellen Maschine immer noch notwendig ist

おすすめ

転載: blog.csdn.net/qq_20255275/article/details/131532449