Virtuelle Welt Intel. Teil 2: SMP

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).

Bild
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ützung

Ich 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:

  1. Spinlock-Initialisierung
  2. Initialisieren und Laden von GDT- und IDT-Tabellen
  3. Paging-Tabellen initialisieren
  4. Initialisieren von VMCS-Strukturen und Erstellen einer gemeinsamen EPT-Tabelle
  5. 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.
  6. Starten des Gastbetriebssystems um 0x7C00 (win7.mbr-Modul)

Initialisierungs-AP:

  1. 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
  2. Laden Sie IDT, GDT sowie den Katalog der Paging-Tabellen herunter, die während der BSP-Initialisierungsphase erstellt wurden
  3. Initialisierung von VMCS-Strukturen und Laden von EPT-Tabellen, die in der Initialisierungsphase von BSP erstellt wurden
  4. 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 Gastbetriebssystem

Ehrlich 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 .

 // 174H 372 IA32_SYSENTER_CS SYSENTER_CS write mask ptrMSR_BMP[0x100 + (0x174 >> 6)] |= (1UL << (0x174 & 0x3F)); // 175H 373 IA32_SYSENTER_ESP SYSENTER_ESP write mask ptrMSR_BMP[0x100 + (0x175 >> 6)] |= (1UL << (0x175 & 0x3F)); // 176H 374 IA32_SYSENTER_EIP SYSENTER_EIP write mask ptrMSR_BMP[0x100 + (0x176 >> 6)] |= (1UL << (0x176 & 0x3F)); 

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 .

 // 174H 372 IA32_SYSENTER_CS SYSENTER_CS read mask ptrMSR_BMP[0x174 >> 6] |= (1UL << (0x174 & 0x3F)); 

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_INFORMATION
SPI_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.



Zusammenfassung

Im 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 hier

Vielen Dank für Ihre Aufmerksamkeit.

Source: https://habr.com/ru/post/de429918/


All Articles