Dlaczego maszyny wirtualne muszą być „maszynami stosowymi” lub „maszynami rejestrującymi” itp.?

48

(To jest pytanie bardzo początkujące).

Studiowałem trochę na temat maszyn wirtualnych.

Okazuje się, że wiele z nich zaprojektowano bardzo podobnie do komputerów fizycznych lub teoretycznych.

Przeczytałem, że na przykład JVM jest „maszyną stosową”. Oznacza to (i popraw mnie, jeśli się mylę), że przechowuje całą swoją „pamięć tymczasową” na stosie i wykonuje operacje na tym stosie dla wszystkich swoich kodów operacyjnych.

Na przykład kod źródłowy 2 + 3zostanie przetłumaczony na kod bajtowy podobny do:

push 2
push 3
add

Moje pytanie brzmi:

JVM są prawdopodobnie napisane przy użyciu C / C ++ i tym podobnych. Jeśli tak, dlaczego JVM nie wykonuje następującego kodu C: 2 + 3..? To znaczy, dlaczego potrzebuje stosu lub innych „rejestrów” maszyn wirtualnych - jak na fizycznym komputerze?

Zajmuje się tym fizyczny procesor. Dlaczego autorzy maszyn wirtualnych nie wykonują po prostu interpretowanego kodu bajtowego z „zwykłymi” instrukcjami w języku, w którym jest zaprogramowana maszyna wirtualna?

Dlaczego maszyny wirtualne muszą emulować sprzęt, skoro sam sprzęt już to dla nas robi?

Znów bardzo nowe pytania. Dzięki za pomoc

Aviv Cohn
źródło
5
Czy zastanawiałeś się, na czym oparte są maszyny inne niż wirtualne?
5
@MichaelT Masz na myśli maszyny fizyczne?
Aviv Cohn
Oczywiście większość maszyn wirtualnych Javascript nie jest maszynami stosowymi ani urządzeniami rejestrującymi - V8 / IonMonkey / Chakra / itp. To maszyny wirtualne, które implementują Javascript. „VM” to tylko interpreter lub kompilator JIT, który może zaimplementować dowolny język, na który chce projektant.
Billy ONeal
@BillyONeal Więc na przykład, jeśli piszę maszynę wirtualną dla jakiegoś języka i piszę ją w C: maszyna wirtualna analizuje wiersz kodu bajtowego „drukuj” cześć ”i wykonuje printf("hi");: czy jest to uważane za maszynę wirtualną? Nie ma „stosu”, „rejestrów” ani niczego.
Aviv Cohn
@Prog: Tak, zgadza się.
Billy ONeal,

Odpowiedzi:

51

Maszyna, wirtualna czy nie, potrzebuje modelu obliczeń, który opisuje sposób wykonywania na nim obliczeń. Z definicji, jak tylko wykonuje obliczenia, implementuje pewien model obliczeń. Pytanie brzmi zatem: jaki model wybrać dla naszej maszyny wirtualnej? Maszyny fizyczne są ograniczone przez to, co można skutecznie i wydajnie wykonać w sprzęcie. Ale, jak zauważasz, maszyny wirtualne nie mają takich ograniczeń, są definiowane w oprogramowaniu przy użyciu arbitralnie języków wysokiego poziomu.

W rzeczywistości istnieją maszyny wirtualne, które są opisane na wysokim poziomie. Nazywa się je językami programowania . Na przykład standard C poświęca większość swoich stron na zdefiniowanie modelu dla tak zwanej „maszyny abstrakcyjnej C”, która opisuje, jak zachowują się programy C, a przez rozszerzenie (reguła as-if), w jaki sposób zgodny kompilator C (lub interpreter) powinien się zachowywać.

Oczywiście zwykle nie nazywamy tego maszyną wirtualną. VM zwykle oznacza coś niższego poziomu, bliżej sprzętu, nieprzeznaczonego do bezpośredniego programowania, zaprojektowanego do wydajnego wykonywania. To odchylenie wyboru oznacza, że ​​coś, co akceptuje kod kompozycyjny wysokiego poziomu (jak to, co opisujesz) nie będzie uważane za maszynę wirtualną, ponieważ wykonuje kod wysokiego poziomu.

Ale, aby przejść do sedna, oto kilka powodów, dla których maszynę wirtualną (jak w, czymś docelowym kompilatora bajtów) opiera się na rejestrze lub tym podobne. Urządzenia do układania i rejestrowania są niezwykle proste. Dla każdej instrukcji istnieje sekwencja instrukcji, niektóre stany i semantyka (funkcja Stan -> Stan). Bez złożonych redukcji drzew, bez pierwszeństwa operatora. Analiza, analiza i wykonywanie jest bardzo proste, ponieważ jest to minimalny język (cukier syntaktyczny jest kompilowany) i zaprojektowany do odczytu maszynowego, a nie ludzkiego.

Natomiast parsowanie nawet najprostszych języków podobnych do C jest dość trudne, a jego wykonanie wymaga nielokalnych analiz, takich jak sprawdzanie i propagowanie typów, rozwiązywanie przeciążeń, utrzymywanie tabeli symboli, rozwiązywanie identyfikatorów ciągów , przekształcanie tekstu liniowego w AST sterowaną pierwszeństwem , i tak dalej. Opiera się na koncepcjach, które są naturalne dla ludzi, ale muszą zostać starannie opracowane przez maszyny.

Na przykład kod bajtowy JVM jest emitowany przez javac. Praktycznie nigdy nie musi być odczytywany ani zapisywany przez ludzi, więc naturalne jest, aby dostosować go do zużycia przez maszyny. Jeśli zoptymalizowany go dla ludzi, JVM byłoby po prostu na każdym starcie odczytać kodu, analizować je, analizować to, a następnie przekształcić go w pośrednim reprezentacji przypominający taki uproszczony model maszyny anyway . Równie dobrze może odciąć środkowego mężczyznę.


źródło
Więc mówisz, że kompilacja wszystkiego do instrukcji na stosie (tj. System.out.println("hi");Jest kompilowana do niektórych instrukcji na stosie, int a = 7jest kompilowana do instrukcji na stosie itp.) Sprawia, że ​​wykonywanie programu jest proste i bardziej wydajne?
Aviv Cohn
2
@Prog Zasadniczo tak. Ale nie tylko wykonanie, także analiza. Wszystko, co odbywa się programowo.
Nadal nie rozumiem, dlaczego 2 + 3jest kompilowany push 2 push 3 add. addKrok na końcu jest wykonywany przez JVM i tak przez uruchomienie kodu C 2 + 3. Programiści JVM nie mogą tego zrobić. Dlaczego nie skompilować go 2 + 3i pozwolić JVM po prostu wykonać kod C 2 + 3(zakładając, że jest napisany w C) od razu?
Aviv Cohn,
@Prog Autor JVM nie może po prostu pisać 2 + 3w kodzie źródłowym JVM, ponieważ JVM musi współpracować z dowolnym programem wykonującym dowolne operacje w dowolnej kolejności. Budowanie kodu źródłowego C i odraczanie do implementacji C po prostu popycha ten sam problem do implementacji C (i nie można tego łatwo zrobić, a tym bardziej skutecznie). Musi istnieć jakaś struktura danych opisująca program, aby można go było interpretować i kompilować JIT, a „kod źródłowy czytelny dla człowieka” to okropny wybór struktury danych z powodów przedstawionych powyżej.
7
@Prog Wydajesz się zbyt skoncentrowany na konkretnym przypadku 2 + 3. A co z a + b? Następnie wartości, które należy dodać, nie pochodzą i.argument{1,2}, są one ładowane ze zmiennych lokalnych. Co frobnicate(x[i]) + (Foo.bar() * 2)? Korzystając z tego projektu, istnieje tylko jedna addoperacja (dla int) i działa ona niezależnie od sposobu obliczania dodatków. Ponadto instrukcja, która dodaje tylko literały całkowite, byłaby bezcelowa: jej wynik mógłby równie dobrze zostać wstępnie obliczony (tzn. Zamiast add(2,3)tego powinien być push(5)).
20

Ta odpowiedź dotyczy JVM, ale w rzeczywistości dotyczy dowolnej maszyny wirtualnej.

Dlaczego maszyny wirtualne muszą emulować sprzęt, skoro sam sprzęt już to dla nas robi?

Nie robią tego, ale sprawia, że ​​maszyna wirtualna jest znacznie prostsza i przenośna: maszyna wirtualna, która emuluje sprzęt, może korzystać z tego samego modelu obliczeniowego, co dowolny sprzętowy procesor.

W szczególności JVM został zbudowany z myślą o przenośności, w rzeczywistości został zbudowany, aby mógł być nawet zaimplementowany w sprzęcie (dziś trudno w to uwierzyć, ale Java powstała w świecie osadzonym - w szczególności w kontrolerach telewizji interaktywnej ).

Jeśli masz taki cel, pożądane jest, aby maszyna wirtualna działała jak najbliżej fizycznej maszyny, ponieważ tłumaczenie na rzeczywisty kod maszynowy staje się łatwiejsze, a tym samym szybsze. Gdy masz już kody maszyn wirtualnych, teoretycznie wszystko, co musisz zrobić, to przetłumaczyć na kody procesora, na którym program faktycznie działa. W praktyce nie jest to takie proste.

To znaczy, dlaczego potrzebuje stosu lub innych „rejestrów” maszyn wirtualnych - jak na fizycznym komputerze?

Zastosowanie modelu maszyny wirtualnej opartej na stosie ma tę zaletę, że można ją łatwo przenieść zarówno na maszyny rejestrujące, jak i na stos, podczas gdy odwrotnie nie jest to prawda. Maszyna wirtualna oparta na rejestrach musiałaby przyjąć założenia dotyczące liczby rejestrów, wielkości rejestrów itp. W przypadku maszyny stosowej takie założenia nie są konieczne.

Zajmuje się tym fizyczny procesor. Dlaczego autorzy maszyn wirtualnych nie wykonują po prostu interpretowanego kodu bajtowego z „zwykłymi” instrukcjami w języku, w którym jest zaprogramowana maszyna wirtualna?

Tak właśnie robią takie maszyny wirtualne, interpretują kod bajtowy. Nawet JVM faktycznie to robi, przynajmniej przed uruchomieniem JIT (just-in-time): interpretuje kody bajtów i wykonuje instrukcje w języku, w którym JVM został napisany (zazwyczaj C lub C ++, ale jest nawet jeden napisany w JavaScript, Doppio ). Zauważ jednak, że nawet takie instrukcje zostały przetłumaczone na kod maszynowy przez kompilator i faktycznie wyglądają bardzo podobnie do tego, co wytwarza kompilator Java - mianowicie używają rejestrów i stosu do wykonywania swojej pracy. Zauważ, że użycie języków „interpretowanych” a „skompilowanych” staje się w tym momencie nieco rozmyte .

miraculixx
źródło
Oczywiście wszystko, co można zaimplementować w oprogramowaniu, można zaimplementować sprzętowo. Ponadto JVM obecnie (hotspot) jest kompilatorem JIT - nie wykonuje instrukcji w języku, w którym została napisana JVM. Gdyby tak się stało, Java działałaby strasznie i nie byłaby tak realistyczna jak dziś platforma . (Do diabła, większość implementacji Javascript byłaby szybsza)
Billy ONeal
2
@BillyONeal "Zamiast kompilować metodę metodą, w samą porę Java HotSpot VM natychmiast uruchamia program za pomocą interpretera i analizuje kod w trakcie jego działania w celu wykrycia krytycznych hotspotów w programie. Następnie skupia uwagę globalny optymalizator natywnego kodu w gorących punktach. ”cytowany z oracle.com/technetwork/java/whitepaper-135217.html#2 , sekcja„ Wykrywanie gorących punktów ”
miraculixx
Tak. „Optymalizator kodu natywnego” == Kompilacja JIT. Istnieje faza interpretera kodu, która nie wydaje się być „gorąca”, aby uniknąć rzadko używanych elementów JIT. Ale to nie znaczy, że JITing wcale się nie odbywa.
Billy ONeal
Dzięki za odpowiedź. Z twojej odpowiedzi zebrałem to, że powodem emulacji sprzętu w maszynie wirtualnej (znanej również jako „stosy” lub „rejestry” itp.) Jest to, że ułatwia późniejszą kompilację kodu bajtowego lub kodu źródłowego do rzeczywistego kodu maszynowego fizyczny procesor. Jednak oprócz tego - czy można coś zyskać na emulacji sprzętu w maszynie wirtualnej? Nadal nie rozumiem, dlaczego ktoś projektujący maszynę wirtualną pomyślałby w kategoriach „maszyny stosowej” lub „maszyny rejestrującej” itp., Kiedy w rzeczywistości mówimy o oprogramowaniu. Czy coś brakuje?
Aviv Cohn
@Prog Ok, masz język programowania, powiedz X. Jak będziesz uruchamiał jego programy? Możesz albo zinterpretować kod źródłowy, albo skompilować go do kodu maszynowego lub skompilować do jakiegoś kodu pośredniego. Teraz masz inny język programowania, Y, i chcesz go zaimplementować za pomocą X. Jeśli obie implementacje są interpreterami, będziesz mieć interpreter Y działający na interpreter X, a to będzie bardzo wolne.
18446744073709551615
11

Dlaczego maszyny wirtualne muszą być „maszynami stosowymi” lub „maszynami rejestrującymi” itp.?

Oni nie. Jeśli potrzebujesz maszyny wirtualnej, może to być cokolwiek.

Istniejące maszyny wirtualne pojawiły się jako rozwiązania takich sytuacji: Naprawdę genialny pomysł przyszedł mi do głowy, wymyśliłem nowy język programowania! Ale muszę wygenerować kod. (Co za nudne zadanie!) Ale nie chcę generować kodu i8086, ponieważ jest brzydki, i nie chcę generować kodu 68k, ponieważ wszyscy inni używają Intela. Istnieje również VAX, ale nie mam żadnego VAX, ani komputera, ani książki VAX. Dlatego wygeneruję kod dla jakiegoś procesora, który fizycznie nie istnieje i zaimplementuję ten procesor w oprogramowaniu. Specyfikacja tej maszyny wirtualnej stworzy rozdział w mojej pracy magisterskiej. Teoretycznie możliwe będzie skompilowanie go do natywnego kodu dowolnego procesora, ale to nie będę ja.

Z drugiej strony, notacja typu „2 + 3” prawdopodobnie nie będzie używana przez maszyny wirtualne w dającej się przewidzieć przyszłości, ponieważ oznacza to konieczność przeprowadzenia dużej transformacji, zanim coś zostanie wykonane.

18446744073709551615
źródło
Dzięki za odpowiedź. Z twojej odpowiedzi zebrałem więc, że motywacją do zaprojektowania maszyny wirtualnej, która emuluje fizyczne procesory, jest to, że ułatwia późniejsze wdrożenie kompilatorów, które kompilują się do rzeczywistego kodu maszynowego. Ale poza tym - czy są jakieś zalety projektowania maszyny wirtualnej pod względem „maszyny stosowej” lub „maszyny rejestrującej” itp.?
Aviv Cohn
1
Rejestry wymagają algorytmów alokacji rejestrów, które wymagają zarówno teorii, jak i debugowania. Maszyna stosu (szczególnie operand zerowy) może po prostu umieścić dane na stosie. OTOH, sprzęt zwykle implementuje ograniczoną liczbę rejestrów zamiast stosu o zmiennej wielkości. Tak więc stosy są łatwiejsze dla oprogramowania, rejestry są łatwiejsze dla sprzętu i prawdopodobnie dlatego są nieco szybsze.
18446744073709551615
-2

Aby odpowiedzieć na zadane pytanie. Termin „wirtualna maszyna” oznacza, że ​​WSZYSTKIE oprogramowanie / sprzęt są symulowane / emulowane. Jeśli korzystasz z oprogramowania / sprzętu, aby wykonać instrukcje, nie masz maszyny wirtualnej, masz kompilator / interpreter.

Kyrelel
źródło
czy to tylko Twoja opinia, czy możesz to jakoś poprzeć?
komar
@ Kyrelel to nieprawda. Sprzęt „ALL” jest emulowany w „systemowej” lub „pełnej” maszynie wirtualnej. Nie wszystkie maszyny wirtualne są pełne. Na przykład warstwa VMD BSD jest nazywana „maszyną wirtualną”, mimo że nie jest tam emulowany sprzęt.
Netch
Nie sądzę, aby pytanie dotyczyło terminologii, ale raczej dlaczego maszyny wirtualne implementują funkcje, które najwyraźniej są już obsługiwane przez rzeczywisty sprzęt
Ryan