3 Punkte von GN⁺ 2023-07-22 | 1 Kommentare | Auf WhatsApp teilen
  • rjvm, ein Side-Project zum Lernen von Rust, ist zwar eine JVM als Lern- und Toy-Projekt, implementiert aber tatsächlich das Einlesen echter .class- und jar-Dateien sowie die Ausführung von Java-Bytecode
  • Nicht unterstützt werden Threads, Reflection, Annotationen, I/O, ein JIT-Compiler und String Interning, aber Kontrollfluss, Objekterzeugung, Methodenaufrufe, Exceptions und Garbage Collection sind enthalten
  • Die Beispiele verwenden das echte rt.jar aus OpenJDK 7, sodass Klassen wie java.lang.StackTraceElement aus einem echten JDK geladen werden
  • Der Code ist in die drei Rust-Crates reader, vm und vm_cli aufgeteilt und behandelt alles von .class-Parsing über den Call Stack und Native Methods bis hin zur Modellierung von Werten und Objekten
  • Der letzte Meilenstein war ein stop-the-world semispace copying collector; nach Erreichen des Ziels wird das Projekt eingestellt, und es gibt auch keinen Plan, bekannte Bugs zu beheben

Die Lern-Rust-JVM rjvm

  • rjvm ist eine Toy Java Virtual Machine, die zum Lernen von Rust gebaut wurde
  • Der Code ist auf GitHub öffentlich verfügbar
  • Es handelt sich eher um eine Implementierung zu Lernzwecken als um eine ernsthafte JVM-Implementierung; deshalb werden folgende Funktionen nicht unterstützt
    • Threads
    • Reflection
    • Annotationen
    • I/O
    • JIT-Compiler
    • String Interning
  • Generics standen anfangs auf der Liste der nicht unterstützten Features, funktionieren aber tatsächlich

Wie weit ist die Implementierung?

  • rjvm ist nicht nur ein einfacher Parser, sondern implementiert direkt verschiedene JVM-Funktionen
    • Kontrollfluss wie if und for
    • Primitive Typen und Objekterzeugung
    • Aufrufe virtueller und statischer Methoden
    • Exception-Behandlung
    • Garbage Collection
    • Klassenauflösung aus jar-Dateien
  • Im Testcode sind Beispiele enthalten, die Throwable, Exception und StackTraceElement verwenden
  • Da echtes I/O fehlt, wird statt System.out.println eine Native Method namens tempPrint verwendet
  • Die Beispiele verwenden das echte rt.jar aus OpenJDK 7, und auch java.lang.StackTraceElement stammt aus dem echten JDK

Drei Rust-Crates

  • Das Projekt ist ein normales Rust-Projekt und besteht aus drei Crates
    • reader: liest .class-Dateien ein und enthält Typen, die deren Inhalt modellieren
    • vm: stellt eine ausführbare virtuelle Maschine als Bibliothek bereit
    • vm_cli: enthält einen einfachen Kommandozeilen-Launcher ähnlich der java-Executable
  • Es wird geprüft, den reader-Crate in ein eigenes Repository auszulagern und auf crates.io zu veröffentlichen

Parsing von .class-Dateien

  • Java-Code wird mit javac zu .class-Dateien kompiliert und meist in .jar-Dateien ausgeliefert, die gewöhnlich im zip-Format vorliegen
  • Um Java-Code auszuführen, muss zuerst die vom Compiler erzeugte .class-Datei geladen werden, die den Bytecode enthält
  • Eine Class-Datei enthält außerdem Informationen, die für Ausführung und Typauflösung benötigt werden
    • Metadaten wie Klassenname und Name der Quelldatei
    • Name der Oberklasse
    • implementierte Interfaces
    • Felder und Feldtypen, Annotationen
    • Method Descriptors, throws-Klauseln, Annotationen, Generics-Informationen
    • Bytecode, Exception-Handler-Tabellen, Zeilennummern-Tabellen
  • Der reader-Crate parst Class-Dateien und gibt eine Rust-Struct zurück, die die Klasse und ihren Inhalt modelliert

Methodenausführung und Call Stack

  • Die wichtigste API des vm-Crates ist Vm::invoke, die zum Ausführen von Methoden verwendet wird
  • Jede laufende Methode besitzt im CallStack genau einen CallFrame
  • Beim Ausführen von main ist der Call Stack anfangs leer, und für die Ausführung wird ein neuer Frame angelegt
  • Bei jedem Funktionsaufruf wird ein neuer Frame zum Call Stack hinzugefügt; wenn die Methodenausführung endet, wird dieser Frame wieder entfernt
  • Die meisten Methoden sind in Java-Bytecode implementiert, aber rjvm unterstützt auch Native Methods
    • Native Methods sind keine Java-Bytecode-Methoden, sondern werden direkt von der JVM selbst implementiert
    • Beispiele sind System::currentTimeMillis, System::arraycopy und Throwable::fillInStackTrace
    • In rjvm sind sie als Rust-Funktionen implementiert
  • Die JVM ist eine stackbasierte virtuelle Maschine, daher arbeiten Bytecode-Instruktionen überwiegend auf einem Wertestack
  • Zu jedem Call Frame gehören ein Wertestack und ein Satz lokaler Variablen, die über Indizes identifiziert werden

Modellierung von Werten und Objekten

  • Value modelliert Werte, die in lokalen Variablen, Stack-Elementen oder Objektfeldern gespeichert werden können
  • Value ist als Rust-enum implementiert und umfasst folgende Zustände
    • Uninitialized
    • Int(i32)
    • Long(i64)
    • Float(f32)
    • Double(f64)
    • Object(AbstractObject<'a>)
    • Null
  • Sum Types wie Rust-enum eignen sich gut dafür auszudrücken, dass ein einzelner Wert einer von mehreren Typen sein kann
  • Die Objektspeicherung begann zunächst mit einer einfachen Object-Struct auf Basis von Vec<Value>, die eine Klassenreferenz und Feldwerte enthält
  • Nach der Implementierung des Garbage Collectors wurde sie durch eine Low-Level-Implementierung mit vielen Pointern und Casts ersetzt
  • Das aktuelle AbstractObject modelliert reale Objekte oder Arrays und ist ein Byte-Array-Pointer mit einigen Header-Words und den Feldwerten

Ausführung von Bytecode-Instruktionen

  • Die Ausführung einer Methode ist ein Prozess, bei dem Bytecode-Instruktionen nacheinander verarbeitet werden
  • Es gibt mehr als 200 JVM-Instruktionen, und sie sind im Bytecode als 1 Byte kodiert
  • Viele Instruktionen haben nachfolgende Argumente, und einige sind variabel lang
  • rjvm modelliert Java-Bytecode-Instruktionen mit dem Typ Instruction
  • Bei der Methodenausführung werden ein Wertestack und ein Array lokaler Variablen geführt, und der Program Counter für die nächste auszuführende Instruktion wird mit 0 initialisiert
  • Normalerweise wird der Program Counter nach einer Instruktion auf die nächste Instruktion gesetzt, Sprunginstruktionen können ihn aber an eine andere Position verschieben
  • Sprunginstruktionen werden zur Implementierung von Kontrollflusskonstrukten wie if, for und while verwendet
  • Es gibt außerdem eine eigene Instruktionsfamilie für Aufrufe anderer Methoden
    • Die Entscheidung, welche Methode aufgerufen wird, kann etwa über virtuelle oder statische Auflösung erfolgen
    • Nach der Auflösung der Zielmethode wird ein neuer Frame zum Call Stack hinzugefügt und die Ausführung beginnt
    • Ist der Rückgabewert nicht void, wird er auf den Stack gepusht und die Ausführung fortgesetzt

Exception-Behandlung

  • Exceptions durchbrechen den normalen Kontrollfluss, führen zu einem vorzeitigen Return aus einer Methode und können sich über den Call Stack ausbreiten; das macht die Implementierung komplex
  • Jeder catch-Block entspricht einem Eintrag in der Exception-Tabelle einer Methode
  • Ein Eintrag in der Exception-Tabelle enthält die Informationen, die nötig sind, um einen Handler zu finden
    • den gültigen Bereich des Program Counters
    • die Adresse der ersten Instruktion des catch-Blocks
    • den Namen der Exception-Klasse, die der Block abfängt
  • CallFrame::execute_instruction verwendet Rusts Result, um das Ergebnis der Instruktionsausführung darzustellen
  • Das Ergebnis der Instruktionsausführung ist in vier Zustände unterteilt
    • erfolgreich, und die aktuelle Methodenausführung wird fortgesetzt
    • erfolgreich, und wegen einer return-Instruktion endet die aktuelle Methode mit einem Rückgabewert
    • Ausführung wegen eines internen VM-Fehlers fehlgeschlagen
    • Ausführung wegen einer geworfenen Java-Exception fehlgeschlagen
  • Die Methodenausführungsschleife parst eine Instruktion, setzt den Program Counter auf die nächste Adresse und führt dann die Instruktion aus
  • Tritt eine Exception auf, wird ein passender Exception-Handler für die aktuelle Instruktionsposition gesucht
    • Gibt es keinen Handler, wird die Exception an den Aufrufer weitergereicht
    • Gibt es einen Handler, wird das Exception-Objekt erneut auf den Stack gepusht und die Ausführung an der Position des catch-Handlers fortgesetzt
  • Rusts Result und Pattern Matching eignen sich gut dazu, dieses Verhalten in der Codestruktur abzubilden

Garbage Collection

  • Der letzte große Meilenstein von rjvm war die Implementierung eines Garbage Collectors
  • Als Algorithmus wurde ein stop-the-world semispace copying collector gewählt
  • Da es keine Threads gibt, ergibt sich ein stop-the-world-Ansatz ganz natürlich
  • Die Implementierung ist eine einfachere Variante von Cheney's algorithm; der Code befindet sich in gc.rs
  • Dieser Ansatz teilt den verfügbaren Speicher in zwei Semispaces
    • einer ist der aktive Bereich für Objektallokationen
    • der andere bleibt ungenutzt
    • ist der aktive Bereich voll, werden lebende Objekte in den anderen Semispace kopiert
    • alle Objektreferenzen werden so aktualisiert, dass sie auf die neuen Kopien zeigen
    • anschließend werden die Rollen der beiden Semispaces vertauscht
  • Das Verfahren wird als ähnlich zu Blue-Green Deployment beschrieben
  • Die Vor- und Nachteile des Algorithmus sind klar
    • wegen des Halbierungsprinzips kann höchstens die Hälfte des Speichers genutzt werden, was zu erheblicher Speicherverschwendung führt
    • Allokation ist sehr schnell, da nur ein Pointer erhöht wird
    • durch das Kopieren und Kompaktieren von Objekten muss Speicherfragmentierung nicht gesondert behandelt werden
    • die Objektkompaktierung kann durch bessere Cache-Line-Nutzung die Performance steigern
  • Echte Java-VMs verwenden normalerweise ausgefeiltere generational garbage collectors wie G1 oder Parallel GC

Das Ende des Projekts

  • Beim Bau von rjvm wurden viel über Rust und die Implementierung virtueller Maschinen gelernt, besonders zufriedenstellend war die Umsetzung eines tatsächlich funktionierenden Garbage Collectors
  • Der Garbage Collector ist zwar nicht besonders ausgereift, funktioniert aber tatsächlich
  • Da das ursprüngliche Ziel erreicht wurde, wird das Projekt an dieser Stelle beendet
  • Es gibt bekannte Bugs, aber keinen Plan, sie zu beheben
  • Rust war eine Sprache, die beim Implementieren einer JVM viel Freude gemacht hat; in weiteren Beiträgen soll rjvm und die Funktionsweise der JVM noch detaillierter behandelt werden

1 Kommentare

 
GN⁺ 2023-07-22
Hacker-News-Kommentare
  • Der schwierige Teil bei der Implementierung eines Garbage Collectors ist sicherzustellen, dass alle Referenzen korrekt als Wurzeln erfasst werden, besonders bei einem verschiebenden Collector.
    Die Methode do_garbage_collection ist als unsafe markiert[1], aber es gibt keine Erklärung, was der Aufrufer garantieren muss, damit sie sicher aufgerufen werden kann.
    Ich frage mich, wie sichergestellt wird, dass alle Referenzen auf den Heap als Wurzeln erfasst werden, und das ist kein triviales Problem[2][3][4].
    Ich habe außerdem das Repository geklont und cargo test ausgeführt, und alle Tests schlagen mit dem Fehler should be able to add entries to the classpath: InvalidEntry(".../vm/rt.jar") fehl: vm/tests/integration/real_code_tests.rs:15:10
    [1] https://github.com/andreabergia/rjvm/blob/be9c54066c64a82879...
    [2] https://manishearth.github.io/blog/2021/04/05/a-tour-of-safe...
    [3] https://without.boats/blog/shifgrethor-iii/
    [4] https://coredumped.dev/2022/04/11/implementing-a-safe-garbag...

    • Diese VM hält statt des nativen Call-Stacks ihren eigenen virtuellen Call-Stack, was ziemlich naheliegend ist.
      Daher kann sie diesen Stack durchlaufen, um Parameter und lokale Variablen zu finden und als Wurzeln zu verwenden.
      Dieser Ansatz kostet Performance, macht das Nachverfolgen für den Garbage Collector aber viel einfacher und erleichtert auch die Implementierung von Nebenläufigkeits- und Kontrollfluss-Primitiven wie Coroutines oder Continuations.
    • Ein Garbage Collector ist im Grunde genommen nur sehr sorgfältige Buchhaltung und daher ziemlich einfach, aber in dem Moment, in dem man mit konkurrierender Garbage Collection anfängt, wird es höllisch schwer.
  • Tolles Projekt, Glückwunsch dazu.
    Allerdings wirkt der Punkt „Nicht unterstützt: Generics“ etwas seltsam.
    Ich frage mich, was es überhaupt bedeuten soll, Generics in einer JVM zu unterstützen.
    Auf Bytecode-Ebene müsste man wegen Type Erasure doch einfach alles als Object, also als Referenztyp, behandeln können, oder? Oder ist der Parser für Klassendefinitionen gemeint? Selbst dann scheint es abgesehen von der Grundsyntax keine Logik zu geben, die prüft, ob die Class Files gültig sind.

    • Zu Generics kam auf reddit dieselbe Frage auf, und das stimmt auch.
      Eigentlich muss man nur das Signature-Attribut lesen, das die Generic-Informationen von Klassen, Methoden und Feldern enthält (https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.ht...).
      Ich habe es gerade tatsächlich getestet, und auch der folgende Code funktioniert :-)
      public class Generic { public static void main(String[] args) { List strings = new ArrayList(10); strings.add("hey"); strings.add("hackernews"); for (String s : strings) { tempPrint(s); } } private static native void tempPrint(String value); }
    • Vielleicht ist checkcast gemeint: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.ht...
      Das wird zum Beispiel erzeugt, wenn man Code wie final Main value = list.get(0); schreibt.
      http://henrikeichenhardt.blogspot.com/2013/05/how-are-java-g...
    • Im Großen und Ganzen stimmt das. Generics wirken sich in seltenen Fällen auf Reflection aus, aber Reflection wird ebenfalls nicht unterstützt, und im Allgemeinen wird beim Kompilieren durch die nächstliegende Klasse oder das nächstliegende Interface ersetzt.
      Dass es dagegen kein String-Interning gibt, ist sehr seltsam. Die Implementierung ist auch eher trivial, und ohne das ist es schwer, das noch eine JVM zu nennen.
      Dass Strings nach Referenz gleich werden, ist wichtig und Teil der JLS.
      Dass es keine Threads gibt, macht den gesamten Versuch eher zu einem Spielzeugprojekt.
  • Wirklich großartig. Als ich 1992 zu den Java-Arbeiten stieß, hieß es noch Oak, und die Gruppe, in der ich war, schaute in die Richtung, ein komplettes Betriebssystem in Java zu schreiben
    Die Idee war, die Angriffsfläche eines eingebetteten Betriebssystems zu verringern, indem nur das absolut Nötige als „Maschinencode“, also als native Methoden, übrig bleibt
    Ursprünglich war Java dafür gedacht, auf Dingen wie Fernsehern oder Haushaltsgeräten zu laufen, und damals wurden die nativen Methoden nicht in Rust, sondern in C geschrieben
    Eine in Rust geschriebene JVM bringt in diesen gesamten Prozess ziemlich starke Speichersicherheit ein

  • Sehenswert ist auch https://jacobin.org/ als in Go geschriebene Implementierung von JVM 17

    • Es gibt auch https://github.com/lihaoyi/Metascala, eine JVM, die in Scala implementiert ist und auf der JVM läuft
    • Als Name für ein Programmierprojekt ist das ziemlich interessant
      Die Jacobiner waren ein revolutionärer politischer Klub der Französischen Revolution der 1790er Jahre und sind auch der Name des Magazins auf https://jacobin.com
  • Ich frage mich, ob man wegen des Lifetime-Parameters in dieser Signatur an Grenzen gestoßen ist
    fn execute_instruction(&mut self, vm: &mut Vm<'a>, call_stack: &mut CallStack<'a>, instruction: Instruction) -> Result, MethodCallFailed<'a>>
    Wenn man der Err-Variante von Result eine Lifetime hinzufügt und diese Lifetime invariant ist, wie hier wegen vm und call_stack, kann man normalerweise weder den ?-Operator noch frühe Rückgaben verwenden[1]
    Dadurch wird die Fehlerbehandlung ausführlicher und schwerer lesbar, deshalb frage ich mich, ob das in der Praxis auch so war
    [1] https://users.rust-lang.org/t/nll-and-early-return-not-allow...

  • Ein großartiges Lernprojekt, und es ist schön zu sehen, dass der Autor Spaß daran hat
    Eine VM von Grund auf zu implementieren macht wirklich Spaß, und ich habe bei solcher Arbeit früher viel gelernt
    Wenn du einmal Garbage Collection anbinden willst, könnte sich ein Blick auf MMTk lohnen (https://www.mmtk.io/)
    Es bietet hochwertige Sammelalgorithmen, die dafür ausgelegt sind, in verschiedene VMs eingesteckt zu werden, und ist in Rust geschrieben

    • Der Vollständigkeit halber: MMTk ist nur für x86
      Ich wollte es einmal für ein Spielzeugprojekt verwenden, habe dann aber aufgegeben, weil ich einen Mac habe
  • Sehr gut gemacht
    Eine VM zu bauen macht immer Spaß, und zusammen mit Rusts Typsystem dürfte das eine interessante Lernerfahrung gewesen sein
    Falls du auf Jobsuche bist, kannst du mich gern über Twitter, Mastodon oder die Firmen-E-Mail kontaktieren. Anhand der User-ID hier solltest du mich finden können.

  • Wenn ich solche großartigen Projekte sehe, fühle ich mich regelrecht überwältigt.
    Es wäre schön, wenn der ursprüngliche Autor erklären würde, wie man mit Rust anfängt und sich ein gewisses Fundament aneignet, um so etwas versuchen zu können.

    • Geht mir genauso.
      Ich möchte nicht zu sehr abschweifen, aber persönlich ringe ich in letzter Zeit ziemlich mit genau diesem Gefühl.
      Ich arbeite seit fast 10 Jahren als professioneller Softwareentwickler, und angesichts meiner aktuellen Rolle und meiner tatsächlichen Fähigkeit, Produkte auszuliefern, weiß ich, dass ich kompetent bin und kein Fake-Entwickler.
      Wenn ich mir in letzter Zeit aber Entwicklerblogs anschaue, bin ich überwältigt, als wüsste ich nicht genug und wäre kein „echter“ Entwickler.
      Ich glaube, dieses Gefühl entsteht, weil ich mir ein ideales Bild eines Entwicklers im Kopf aufgebaut habe und mich mit diesem eingebildeten Maßstab vergleiche.
      Ich bewundere Menschen mit viel tiefem Wissen, die sich klar und prägnant ausdrücken, und frage mich gleichzeitig, warum ich nicht so bin.
      Wenn die Arbeit vorbei ist und ich mich um die Familie gekümmert habe, bleibt fast keine Energie mehr, noch irgendetwas zu tun, und ich weiß auch, dass Programmieren nicht alles ist, aber ich möchte trotzdem mehr lernen und wachsen.
      Ich weiß, dass das weder gesund noch vernünftig ist, aber in letzter Zeit ist dieses Gefühl schwer abzuschütteln.
    • Ehrlich gesagt habe ich jedes Mal, wenn ich HN öffne, ungefähr zur Hälfte Impostor-Syndrom.
      Früher hatte ich ein wenig VM-Erfahrung und habe vor ein paar Jahren sogar eine kurze Blogserie darüber geschrieben.
      In einem früheren Job habe ich auch ein wenig an JVM-Bytecode herumgespielt, um ein sehr ungewöhnliches Kundenproblem zu lösen.
      Außerdem habe ich vor ein paar Jahren das großartige https://craftinginterpreters.com/ gelesen und mir dort Ideen geholt.
      Aber dieses Projekt war eindeutig groß und komplex.
      Es hat viel Zeit gekostet und lag wie viele meiner Side Projects auch ein paar Mal brach, aber ich freue mich, dass ich es fertiggestellt habe :-)
    • Ich bin kein Rust-Experte und auch nicht der ursprüngliche Autor, aber ich kann ein anderes Thema als Beispiel nennen: Sockets.
      Ich habe mich vor Kurzem intensiv mit Sockets beschäftigt, aber noch vor zwei Wochen hatte ich nur ein grobes Verständnis auf hoher Ebene, das ich mir durch überflogenene Manpages, Dokumentation und Blogposts angeeignet hatte.
      Ich wollte die Grundlagen von Networking verstehen und habe beschlossen, so viel wie möglich zu lesen; eine Woche später hatte ich genug gelernt, um Socket-Code in Python und C zu schreiben.
      Da ich Python ziemlich gut kann, habe ich nach dem tiefen Einarbeiten auch die sockets-Bibliothek besser verstanden.
      Wenn du Technik A in Sprache X besser beherrschen willst, würde ich empfehlen, so viel wie möglich über Technik A zu lesen oder anzuschauen und etwas damit in Sprache Y zu bauen.
      Wenn du dann zu Sprache X zurückkehrst, bist du mit den Konzepten rund um Technik A bereits ziemlich vertraut.
    • Man muss es nur in kleine Teile zerlegen.
      Eine einfache Sprach-VM besteht aus einer Darstellung von Objekten im Speicher, einem Bytecode-Interpreter, einem einfachen Garbage Collector und einem Loader.
      Den Bytecode-Interpreter kann man als Stack betrachten, als eine Art, Funktionen auf diesem Stack darzustellen, und als Schleife, die jeden Bytecode interpretiert und den Programmzähler weiterbewegt.
    • Wichtig ist, wie viel man in der Freizeit programmiert, also wie viele Stunden pro Woche im Durchschnitt.
      Wenn es 0 Stunden sind, ist das natürlich nichts, wofür man kritisiert werden sollte, und es gibt auch viele andere Dinge, auf die man sich konzentrieren muss. Aber wenn jemand über Jahre hinweg im Schnitt 10–20 Stunden pro Woche an Side Projects arbeitet, ist es nicht überraschend, dass dabei beeindruckende Ergebnisse herauskommen.
  • Um schamlos ein ähnliches Projekt zu bewerben: https://github.com/tenaf0/rust-jvm3

  • Ich entwickle ein freies Betriebssystem für 386(486)-AT-Klone. Es ist nur ein Hobby und nichts Großes und Professionelles wie GNU :-)

    • Es gibt ein no-std-Tutorial, in dem ein Demo-Kernel in Rust geschrieben wird: https://os.phil-opp.com
      osdev.org, sandpile.org, RBIL und freevga sind ebenfalls nützliche Referenzen.
      Der größte Problembereich ist die Hardware-Unterstützung.
      Es gibt auch viele gute alte gedruckte Bücher mit Rezepten für Dinge wie zuverlässiges Port-I/O oder undokumentierte Hardware-Tricks.
      Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4
      Microsoft MS-DOS Programmer's Reference, einschließlich real-mode-BIOS-Aufrufen
      PC Interrupts
      Undocumented PC
      PC Intern
      Programmer's Guide To The EGA, VGA, And Super VGA Cards
      Graphics Programming Black Book Special Edition
      Es lohnt sich außerdem, Fortschritte in der Betriebssystementwicklung nach der Ära von Monokernel, Mikrokernel und Hybridansätzen experimentell auszuprobieren.
      Capability-basierte Architekturen wie seL4 haben mehrere inhärente Vorteile bei Performance und Sicherheit, darunter Capabilities und hervorragende IPC.
      Eine POSIX-kompatible Schicht ist ebenfalls wichtig. Selbst eingebettete Betriebssysteme ohne Thread- oder Prozesskonzepte können POSIX implementieren.
      Ein Hypervisor lässt sich mit Intel VT-[xd] deutlich leichter ergänzen; ohne das kann man auf Emulation zurückfallen. Übersetzende Emulation bietet sehr gute Performance.
      Man muss darin gut werden, Interrupt-Handler zu verallgemeinern und schnell zu machen, Race Conditions zu vermeiden und Lock-Free-Patterns zu verwenden.
      Es ist auch nötig, nicht unterstützte Instruktionen einschließlich x87 und MMX umzuschreiben oder abzufangen.
      Der Grund, warum reine Mikrokernel gescheitert sind, war die gewachsene Komplexität bei der transaktionalen Reihenfolge und Verwaltung vieler Ressourcen.
      Mikrokernel-Architekturen haben theoretisch große Vorteile in Sicherheit und Betrieb, konnten sich in ihrer reinen Form aber nicht breit durchsetzen.