In einem früheren
Artikel (Link) habe ich über das Grundkonzept eines Hypervisors gesprochen, der auf der Intel-Hardwarevirtualisierungstechnologie basiert. Jetzt schlage ich vor, die Funktionen des Hypervisors durch Unterstützung der Multiprozessor-Architektur (SMP) zu erweitern und ein Beispiel dafür zu betrachten, wie der Hypervisor Änderungen am Gastbetriebssystem vornehmen kann.
Alle weiteren Aktionen werden auf dem PC mit folgender Konfiguration ausgeführt:
CPU: Intel Core i7 5820K
Hauptplatine: Asus X99-PRO
RAM: 16 GB
Gastbetriebssystem: Windows 7 x32 mit deaktivierter PAE
Zunächst beschreibe ich die Position der Komponenten des Hypervisors auf der Festplatte (alle Werte sind in Sektoren angegeben).
Das Laden eines Hypervisors unterscheidet sich von der vorherigen Version nur durch das Vorhandensein eines neuen Moduls hypervisor.ap , dessen Zweck die grundlegende Initialisierung des AP-Prozessors ist.Der Prozess des Ladens von Modulen in den Speicher:
SMP-UnterstützungIch habe einen Hypervisor nach dem Prinzip der symmetrischen Mehrfachverarbeitung implementiert, was bedeutet, dass auf allen vorhandenen logischen Prozessoren dieselbe Kopie von VMX gestartet wird. Darüber hinaus sind IDT- und GDT-Tabellen sowie Tabellen für den Paging-Speicher allen logischen Prozessoren gemeinsam. Ich habe dies getan, weil der Hypervisor den Speicher für den Adressraum des Gastbetriebssystems sofort initialisiert und die physischen Adressen einzelner Seiten nicht dynamisch neu zugewiesen werden müssen. Bei diesem Ansatz müssen Sie auch nicht die Korrespondenz der Prozessor-TLB-Caches auf der Seite des Hypervisors überwachen.
Der Initialisierungsprozess für BSP und AP ist unterschiedlich. Alle am Hypervisor beteiligten Hauptstrukturen werden während der Initialisierung des BSP erstellt. Darüber hinaus wird der Aktivitätsstatus für vmx-AP-Prozessoren ohne Root-Modus auf den HLT-Status gesetzt. Auf diese Weise wird die Umgebung des Gastbetriebssystems entsprechend der Verwendung ohne Virtualisierung emuliert.
BSP initialisieren:
- Spinlock-Initialisierung
- Initialisieren und Laden von GDT- und IDT-Tabellen
- Paging-Tabellen initialisieren
- Initialisieren von VMCS-Strukturen und Erstellen einer gemeinsamen EPT-Tabelle
- Aktivierung von AP-Prozessoren. Zu diesem Zweck wird an jeden AP eine INIT-SIPI-Interrupt-Sequenz gesendet. Der Vektor für den SIPI-Interrupt ist 0x20, was der Übertragung der AP-Steuerung um 0x20000 (hypervisor.ap-Modul) entspricht.
- Starten des Gastbetriebssystems um 0x7C00 (win7.mbr-Modul)
Initialisierungs-AP:
- Nach dem Aktivieren des AP befindet sich der Prozessor im Real-Modus. Das hypervisor.ap-Modul initialisiert Speicher- und Paging-Tabellen, um in den Langmodus zu wechseln
- Laden Sie IDT, GDT sowie den Katalog der Paging-Tabellen herunter, die während der BSP-Initialisierungsphase erstellt wurden
- Initialisierung von VMCS-Strukturen und Laden von EPT-Tabellen, die in der Initialisierungsphase von BSP erstellt wurden
- Umschalten in den VMX-Nicht-Root-Modus mit aktivem HLT-Status
Wir können sagen, dass die Implementierung der SMP-Unterstützung im Hypervisor recht einfach ist, aber es gibt einige Punkte, auf die ich aufmerksam machen möchte.
1.USB Legacy Support
Neue Motherboard-Modelle verfügen möglicherweise nicht über PS / 2-Anschlüsse. Daher wird USB Legacy Support verwendet, um die Abwärtskompatibilität sicherzustellen. Dies bedeutet, dass Sie mit einer USB-Tastatur oder -Maus mit denselben Methoden (Eingabe- / Ausgabeports) arbeiten können wie mit dem PS / 2-Standard. Die Implementierung des USB Legacy Support hängt nicht nur vom Modell des Motherboards ab, sondern kann auch in verschiedenen Firmware-Versionen angezeigt werden. Auf meinem Asus X99-PRO-Motherboard wird die USB-Legacy-Unterstützung über SMI-Interrupts implementiert, in deren Prozessor die PS / 2-Emulation erfolgt. Ich schreibe so ausführlich darüber, weil in meinem Fall (Firmware-Version 3801) der USB Legacy Support nicht mit dem Long-Modus kompatibel ist und der Prozessor bei der Rückkehr von SMM in den Shutdown-Zustand wechselt.
In dieser Situation ist es am einfachsten, den USB Legacy Support zu deaktivieren, bevor Sie in den Langmodus wechseln. Unter Windows wird die PS / 2-Tastaturabfragemethode jedoch bei der Auswahl der Startoptionen verwendet. Daher muss der USB Legacy-Support erneut aktiviert werden, bevor das Gastbetriebssystem geladen wird.
2. Hardware Task Switch
In modernen Betriebssystemen wird das Umschalten zwischen Aufgaben in der Regel durch Softwaremethoden implementiert. In Windows 7 werden Selektoren, die auf TSS zeigen, Interrupt 2 - NMI und 8 - Double Fault zugewiesen, was bedeutet, dass solche Interrupts zu einer Umschaltung des Hardwarekontexts führen. Intel VMX unterstützt keinen Hardware-Task-Switch, und ein Versuch, ihn auszuführen, führt zum Beenden von VM. In solchen Fällen habe ich meinen Task Switch-Handler (GuestTaskSwitch-Funktion) geschrieben. Ein Double Fault-Interrupt tritt nur im Falle eines schwerwiegenden Systemkonflikts auf, der durch unsachgemäße Behandlung anderer Interrupts verursacht wird. Beim Debuggen bin ich nicht darauf gestoßen. NMI wird jedoch zum Zeitpunkt des Neustarts von Windows auf AP-Prozessoren angezeigt. Dies lässt immer noch meine Zweifel aufkommen, da unklar ist, ob diese NMIs das Ergebnis eines regelmäßigen Neustarts sind oder ob dieser Hypervisor in einigen der vorherigen Phasen falsch funktioniert. Wenn Sie Informationen zu diesem Thema haben, sprechen Sie bitte in den Kommentaren oder schreiben Sie mir in einer persönlichen Nachricht.
Änderungen im GastbetriebssystemEhrlich gesagt konnte ich mich lange nicht genau entscheiden, welche Änderungen der Hypervisor an der Arbeit des Gastbetriebssystems vornehmen sollte. Tatsache ist, dass ich einerseits etwas Interessantes zeigen wollte, wie die Einführung unserer Handler in grundlegende Netzwerkprotokolle, andererseits würde alles auf eine große Menge an Code hinauslaufen, und es gab wenig mit dem Thema eines Hypervisors zu tun. Außerdem wollte ich den Hypervisor nicht an einen bestimmten Satz Eisen binden.
Infolgedessen wurde der folgende Kompromiss gefunden: In dieser Version des Hypervisors ist die Steuerung von Systemaufrufen aus dem Benutzermodus implementiert, dh es ist möglich, den Betrieb von Anwendungen zu steuern, die im Gastbetriebssystem ausgeführt werden. Diese Art der Steuerung ist recht einfach zu implementieren und ermöglicht es Ihnen außerdem, ein visuelles Ergebnis der Arbeit zu erhalten.
Die Kontrolle über den Betrieb von Anwendungen erfolgt auf der Ebene der Systemaufrufe. Das Hauptziel besteht darin, das Ergebnis der Funktion
NtQuerySystemInformation so zu ändern, dass beim Aufruf mit dem Argument
SystemProcessInformation (
0x05 ) Prozessinformationen abgefangen werden können.
In Windows 7 verwendet das Anwendungsprogramm zum Aufrufen der Systemfunktion den Befehl Assembler sysenter.
Anschließend wird die Steuerung an den
KiFastCallEntry- Prozessor an den Kernel auf Ebene r0 übertragen. Verwenden Sie den Befehl sysexit, um zur Anwendungsebene r3 zurückzukehren.
Um Zugriff auf die Ergebnisse der
Ausführung der Funktion
NtQuerySystemInformation zu erhalten, muss die Nummer der aufgerufenen Funktion bei jeder Ausführung des Befehls sysenter
gespeichert werden . Vergleichen Sie dann beim Ausführen von
sysexit den gespeicherten Wert mit der Nummer der abgefangenen Funktion und nehmen Sie bei Übereinstimmung Änderungen an den von der Funktion zurückgegebenen Daten vor.
Intel VMX bietet keine direkte Möglichkeit zur Überwachung der Ausführung von
sysenter / sysexit . Wenn Sie jedoch den Wert 0 in
Guest MSR IA32_SYSENTER_CS schreiben ,
lösen die Befehle sysenter / sysexit eine GP-Ausnahme aus, mit der der VM Exit-Handler
aufgerufen werden kann. Damit die GP-Ausnahme VM Exit aufruft, müssen Sie im Feld
Exception Bitmap von VMCS 13 Bit setzen.
Die folgende Struktur wird verwendet, um das Sysenter / Sysexit-Paar zu emulieren.
typedef struct{ QWORD ServiceNumber; QWORD Guest_Sys_CS; QWORD Guest_Sys_EIP; QWORD Guest_Sys_ESP; } SysEnter_T;
Das Feld
ServiceNumber enthält die Nummer der aufgerufenen Funktion und wird bei jedem Aufruf von sysenter aktualisiert.
Die Felder
Guest_Sys_CS, Guest_Sys_EIP, Guest_Sys_ESP werden aktualisiert, wenn das Gastbetriebssystem versucht, in das entsprechende MSR-Register zu schreiben. Dazu werden Schreibmasken in die
MSR-Bitmap-Adresse gesetzt .
Das Gastbetriebssystem sollte die vom Hypervisor am Betrieb von Systemfunktionsaufrufen vorgenommenen Änderungen nicht sehen. Durch Festlegen der
Lesemaske für
MSR IA32_SYSENTER_CS können Sie das Gastbetriebssystem beim Lesen auf seinen ursprünglichen Registerwert
zurücksetzen .
Das Folgende ist ein
Sysenter / Sysexit- Befehlsemulationsschema.

In der
Sysexit- Emulationsphase wird die gespeicherte Nummer der aufgerufenen Funktion mit der
NtQuerySystemInformation- Nummer (0x105) verglichen. Im Falle einer Übereinstimmung wird überprüft, ob NtQuerySystemInformation mit dem Argument System Process Information aufgerufen wird. In diesem Fall nimmt die Funktion
ChangeProcessNames (DWORD SPI_GVA, DWORD SPI_size) Änderungen an den Strukturen vor, die Informationen zu den Prozessen enthalten.
SPI_GVA ist die virtuelle
Gastadresse der Struktur
SYSTEM_PROCESS_INFORMATIONSPI_size ist die Gesamtgröße der Strukturen in Bytes.
Die Struktur
SYSTEM_PROCESS_INFORMATION selbst sieht folgendermaßen aus:
typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; BYTE Reserved1[48]; UNICODE_STRING ImageName; KPRIORITY BasePriority; HANDLE UniqueProcessId; PVOID Reserved2; ULONG HandleCount; ULONG SessionId; PVOID Reserved3; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG Reserved4; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; PVOID Reserved5; SIZE_T QuotaPagedPoolUsage; PVOID Reserved6; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER Reserved7[6]; } SYSTEM_PROCESS_INFORMATION;
Das Parsen ist nicht kompliziert. Die Hauptsache ist, nicht zu vergessen, die virtuelle
Gastadresse in eine physische zu übersetzen.
Hierfür wird die Funktion
GuestLinAddrToPhysAddr () verwendet.
Aus Gründen der Übersichtlichkeit habe ich die ersten beiden Zeichen in den Namen aller Prozesse durch ein '
:) ' ersetzt. Das Ergebnis einer solchen Ersetzung ist im Screenshot sichtbar.
ZusammenfassungIm Allgemeinen wurden die am Anfang des Artikels festgelegten Aufgaben abgeschlossen. Der Hypervisor stellt den stabilen Betrieb des Gastbetriebssystems sicher und steuert auch den Aufruf von Systemfunktionen auf Anwendungsebene. Ich
stelle fest, dass der Hauptnachteil der Verwendung der
Sysenter / Sysexit- Befehlsemulation eine signifikante Zunahme der VM-Exit-Aufrufe ist, was sich auf die Leistung auswirkt. Dies macht sich insbesondere dann bemerkbar, wenn das Gastbetriebssystem im Uniprozessor-Modus arbeitet. Dieser Nachteil kann beseitigt werden, wenn Sie Aufrufe nur im Kontext der ausgewählten Prozesse steuern.
Und das ist alles für jetzt. Quellen für den Artikel finden Sie
hierVielen Dank für Ihre Aufmerksamkeit.