Einzelner Windows-Kernel

Windows ist eines der vielfältigsten und flexibelsten Betriebssysteme, es funktioniert auf völlig unterschiedlichen Architekturen und ist in verschiedenen Versionen verfügbar. Heute werden x86-, x64-, ARM- und ARM64-Architekturen unterstützt. Windows unterstützte einst Itanium, PowerPC, DEC Alpha und MIPS. Darüber hinaus unterstützt Windows eine Vielzahl von SKUs, die unter verschiedenen Bedingungen ausgeführt werden. Von Rechenzentren, Laptops, Xbox und Telefonen bis hin zu eingebetteten Versionen des Internet der Dinge, beispielsweise in Geldautomaten.

Das Erstaunlichste ist, dass der Windows-Kernel in Abhängigkeit von all diesen Architekturen und SKUs praktisch unverändert bleibt . Der Kernel wird je nach Architektur und Prozessor, auf dem er arbeitet, dynamisch skaliert, um die Geräte optimal nutzen zu können. Natürlich verfügt der Kernel über eine bestimmte Menge an Code, die einer bestimmten Architektur zugeordnet ist, aber es gibt nur eine minimale Menge davon, sodass Windows auf einer Vielzahl von Architekturen ausgeführt werden kann.

In diesem Artikel werde ich über die Entwicklung der wichtigsten Teile des Windows-Kernels sprechen, die es ihm ermöglichen, transparent vom verbrauchsarmen NVidia Tegra-Chip auf Surface RT 2012 bis zu den riesigen Monstern in Azure-Rechenzentren zu skalieren.


Ein Windows-Task-Manager, der auf einem Windows DataCenter-Vorabversionscomputer ausgeführt wird. 896 Kerne unterstützen 1792 logische Prozessoren und 2 TB Speicher

Single Core Evolution


Bevor wir uns mit den Details des Windows-Kernels befassen, wollen wir uns ein wenig mit dem Refactoring befassen . Refactoring spielt eine Schlüsselrolle bei der Erhöhung der Wiederverwendung von Betriebssystemkomponenten auf verschiedenen SKUs und Plattformen (z. B. Client, Server und Telefon). Die Grundidee des Refactorings besteht darin, dass Sie dieselbe DLL auf verschiedenen SKUs wiederverwenden können und dabei kleine Änderungen unterstützen, die speziell für die gewünschte SKU vorgenommen wurden, ohne die DLL umzubenennen und ohne die Arbeit von Anwendungen zu unterbrechen.

Die Kerntechnologie des Windows-Refactorings ist eine wenig dokumentierte Technologie namens API-Sets . API-Suites sind ein Mechanismus, mit dem das Betriebssystem die DLL und ihren Verwendungsort trennen kann. Mit dem API-Set können Anwendungen für win32 beispielsweise weiterhin kernel32.dll verwenden, obwohl die Implementierung aller APIs in einer anderen DLL geschrieben ist. Diese Implementierungs-DLLs können sich auch zwischen SKUs unterscheiden. Sie können die API-Sätze in Aktion sehen, indem Sie die Abhängigkeitsüberquerung auf einer herkömmlichen Windows-DLL ausführen, z. B. kernel32.dll.


Nachdem Sie diesen Exkurs über die Struktur von Windows abgeschlossen haben, der es dem System ermöglicht, die Wiederverwendung und Freigabe von Code zu maximieren, gehen wir zu den technischen Tiefen des Startens des Kernels gemäß dem Scheduler über, der der Schlüssel zur Skalierung des Betriebssystems ist.

Kernel-Komponenten


Windows NT ist in der Tat ein Mikrokernel in dem Sinne, dass es einen eigenen Kernel (KE) mit einem begrenzten Satz von Funktionen hat, der die ausführbare Schicht (Executive Layer, Ex) verwendet, um alle Richtlinien auf hoher Ebene auszuführen. EX ist immer noch im Kernel-Modus, es ist also nicht gerade ein Mikrokernel. Der Kernel ist für das Planen von Threads, das Synchronisieren zwischen Prozessoren, das Behandeln von Ausnahmen auf Hardwareebene und das Implementieren von hardwareabhängigen Funktionen auf niedriger Ebene verantwortlich. Die EX-Schicht enthält verschiedene Subsysteme, die eine Reihe von Funktionen bereitstellen, die normalerweise als Kern betrachtet werden - E / A, Objektmanager, Speichermanager, Prozesssubsystem usw.


Um die Größe der Komponenten besser zu verstehen, finden Sie hier eine ungefähre Aufschlüsselung der Anzahl der Codezeilen in mehreren Schlüsselverzeichnissen des Kernel-Quellbaums (einschließlich Kommentaren). Die Tabelle enthält noch nicht alles, was mit dem Kernel zu tun hat.

Kernel-SubsystemeCodezeilen
Speichermanager501.000
Registrierung211.000
Macht238.000
Exekutive157.000
Sicherheit135.000
Kernel339.000
Subsystem verarbeiten116.000

Weitere Informationen zur Windows-Architektur finden Sie in der Buchreihe Windows Internals .

Planer


Nachdem wir den Boden auf diese Weise vorbereitet haben, wollen wir ein wenig über den Scheduler, seine Entwicklung und darüber sprechen, wie der Windows-Kernel mit so vielen Prozessoren auf so viele verschiedene Architekturen skaliert werden kann.

Ein Thread ist eine Grundeinheit, die Programmcode ausführt, und genau seine Arbeit plant der Windows-Scheduler. Bei der Entscheidung, welcher Thread gestartet werden soll, verwendet der Scheduler seine Prioritäten. Theoretisch sollte der Thread mit der höchsten Priorität auf dem System gestartet werden, auch wenn dies bedeutet, dass für Threads mit niedrigeren Prioritäten keine Zeit mehr bleibt.

Nachdem der Thread die Quantenzeit (die Mindestzeit, die ein Thread arbeiten kann) gearbeitet hat, nimmt die dynamische Priorität ab, so dass Threads mit hoher Priorität nicht für immer funktionieren können, die Seele aller anderen. Wenn ein anderer Thread zur Arbeit aufwacht, erhält er eine Priorität, die auf der Grundlage der Wichtigkeit des Ereignisses berechnet wird, das das Warten verursacht hat (z. B. wird die Priorität für die Front-End-Benutzeroberfläche stark erhöht und nicht viel - um E / A-Vorgänge abzuschließen). Daher arbeitet ein Thread mit hoher Priorität, während er interaktiv bleibt. Wenn es vorwiegend mit Berechnungen verbunden wird (CPU-gebunden), sinkt seine Priorität und sie kehren zu ihm zurück, nachdem andere Threads mit hoher Priorität ihre Prozessorzeit haben. Darüber hinaus erhöht der Kernel willkürlich die Priorität von vorgefertigten Threads, die für einen bestimmten Zeitraum keine Prozessorzeit erhalten haben, um deren Rechenmangel zu verhindern und die Prioritätsinversion zu korrigieren.

Der Windows Scheduler hatte anfangs eine Bereitschaftswarteschlange, aus der er den nächsten Thread mit der höchsten Priorität zum Ausführen auswählte. Mit dem Beginn der Unterstützung für eine zunehmende Anzahl von Prozessoren wurde die einzige Warteschlange jedoch zu einem Engpass, und der Scheduler änderte die Arbeit im Windows Server 2003-Release-Bereich und organisierte eine Bereitschaftswarteschlange pro Prozessor. Beim Wechsel zur Unterstützung mehrerer Anforderungen für einen Prozessor wurde keine einzige globale Sperre durchgeführt, um alle Warteschlangen zu schützen, und der Scheduler konnte Entscheidungen auf der Grundlage lokaler Optima treffen. Dies bedeutet, dass zu jedem Zeitpunkt im System ein Thread mit der höchsten Priorität vorhanden ist, dies bedeutet jedoch nicht unbedingt, dass N der Threads mit der höchsten Priorität in der Liste (wobei N die Anzahl der Prozessoren ist) im System arbeiten. Dieser Ansatz hat sich ausgezahlt, bis Windows auf CPUs mit geringem Stromverbrauch wie Laptops und Tablets umstieg. Wenn der Thread mit den höchsten Prioritäten auf solchen Systemen nicht funktionierte (z. B. der Front-End-Thread der Benutzeroberfläche), führte dies zu merklichen Schnittstellenfehlern. Daher wurde in Windows 8.1 der Scheduler auf ein Hybridmodell übertragen, mit Warteschlangen für jeden Prozessor für Threads, die diesem Prozessor zugeordnet sind, und einer gemeinsam genutzten Warteschlange mit vorgefertigten Prozessen für alle Prozessoren. Dies wirkte sich nicht merklich auf die Leistung aus, da andere Änderungen in der Scheduler-Architektur vorgenommen wurden, z. B. das Refactoring einer Dispatcher-Datenbanksperre.

In Windows 7 wurde ein dynamischer Fair-Share-Scheduler (Dynamic Fair-Share-Scheduler, DFSS) eingeführt. Dies betraf hauptsächlich Terminalserver. Diese Funktion hat versucht, das Problem zu lösen, dass eine Terminalsitzung mit einer hohen CPU-Auslastung Threads in anderen Terminalsitzungen beeinflussen kann. Da der Scheduler Sitzungen nicht berücksichtigte und lediglich die Priorität zum Verteilen von Flows verwendete, konnten Benutzer in verschiedenen Sitzungen die Arbeit von Benutzern in anderen Sitzungen beeinflussen, indem sie ihre Flows erwürgten. Es gab auch Sitzungen (und Benutzern) mit einer großen Anzahl von Threads einen unfairen Vorteil, da eine Sitzung mit einer großen Anzahl von Threads mehr Möglichkeiten hatte, Prozessorzeit zu erhalten. Es wurde versucht, dem Scheduler eine Regel hinzuzufügen, nach der jede Sitzung in Bezug auf die Prozessorzeit den anderen gleichgestellt wurde. Ähnliche Funktionen gibt es unter Linux mit ihrem völlig ehrlichen Scheduler ( Completely Fair Scheduler ). In Windows 8 wurde dieses Konzept als Scheduler-Gruppe verallgemeinert und dem Scheduler hinzugefügt, wodurch jede Sitzung in eine unabhängige Gruppe fiel. Zusätzlich zu den Prioritäten für die Threads verwendet der Scheduler die Scheduler-Gruppen als Index der zweiten Ebene und entscheidet, welcher Thread als Nächstes gestartet werden soll. Auf dem Terminalserver haben alle Scheduler-Gruppen das gleiche Gewicht, sodass alle Sitzungen unabhängig von der Anzahl oder Priorität der Threads innerhalb der Scheduler-Gruppen die gleiche Prozessorzeit erhalten. Darüber hinaus werden solche Gruppen auch zur genaueren Steuerung von Prozessen verwendet. In Windows 8 wurden Jobobjekte erweitert, um die Prozessorzeitverwaltung zu unterstützen. Mithilfe einer speziellen API können Sie entscheiden, wie viel Prozessorzeit ein Prozess verwenden kann, falls es sich um ein weiches oder hartes Limit handelt, und Benachrichtigungen erhalten, wenn der Prozess diese Limits erreicht. Dies ähnelt der Ressourcenverwaltung in cgroups unter Linux.

Ab Windows 7 hat Windows Server die Unterstützung für mehr als 64 logische Prozessoren auf einem einzelnen Computer eingeführt. Um so viele Prozessoren zu unterstützen, wurde im System eine neue Kategorie eingeführt, die „Prozessorgruppe“. Eine Gruppe ist eine unveränderliche Menge von logischen Prozessoren mit nicht mehr als 64 Teilen, die von einem Scheduler als Recheneinheit betrachtet werden. Der Kernel beim Booten bestimmt, welcher Prozessor zu welcher Gruppe gehört, und bei Computern mit weniger als 64 Prozessorkernen ist dieser Ansatz kaum zu bemerken. Ein Prozess kann in mehrere Gruppen unterteilt werden (z. B. eine Instanz von SQL Server). Es kann jeweils nur ein Thread innerhalb derselben Gruppe ausgeführt werden.

Auf Computern mit einer Anzahl von mehr als 64 CPU-Kernen zeigte Windows jedoch neue Engpässe, die verhinderten, dass anspruchsvolle Anwendungen wie SQL Server mit zunehmender Anzahl von Prozessorkernen linear skaliert wurden. Daher zeigten Geschwindigkeitsmessungen selbst mit dem Hinzufügen neuer Kerne und Speicher keinen signifikanten Anstieg. Eines der Hauptprobleme war der Streit um die Blockierung der Dispatcher-Basis. Durch das Sperren der Dispatcher-Datenbank wurde der Zugriff auf Objekte geschützt, deren Arbeit geplant werden musste. Zu diesen Objekten gehören Threads, Timer, Eingabe- / Ausgabeports und andere Kernelobjekte, die warten müssen (Ereignisse, Semaphoren, Mutexe). Unter dem Druck der Notwendigkeit, solche Probleme zu lösen, wurde in Windows 7 daran gearbeitet, die Blockierung der Dispatcher-Datenbank zu beseitigen und durch genauere Anpassungen zu ersetzen, z. B. das blockweise Sperren von Objekten. Auf diese Weise konnten Leistungsmessungen wie SQL TPC-C bei einigen Konfigurationen eine Geschwindigkeitssteigerung von 290% im Vergleich zum vorherigen Schema nachweisen. Es war eine der größten Leistungssteigerungen in der Windows-Geschichte, die aufgrund einer Änderung einer einzelnen Funktion aufgetreten ist.

Windows 10 brachte eine weitere Innovation mit sich, indem es CPU-Sets einführte. Mit CPU-Sets kann ein Prozess ein System partitionieren, sodass ein Prozess auf mehrere Prozessorgruppen verteilt werden kann, sodass andere Prozesse diese nicht verwenden können. Der Windows-Kernel erlaubt nicht einmal Geräte-Interrupts, die in Ihrem Set enthaltenen Prozessoren zu verwenden. Dadurch wird sichergestellt, dass selbst Geräte ihren Code nicht auf Prozessoren ausführen können, die an die Gruppe Ihrer Anwendung ausgegeben wurden. Es sieht aus wie eine virtuelle Low-Tech-Maschine. Es ist klar, dass dies eine leistungsstarke Funktion ist, in die so viele Sicherheitsmaßnahmen integriert sind, dass der Anwendungsentwickler bei der Arbeit mit der API keine großen Fehler macht. Die Funktionalität der CPU-Sets wird im Spielemodus verwendet.

Schließlich kommen wir zur Unterstützung von ARM64, das in Windows 10 erschien . Die ARM-Architektur unterstützt die big.LITTLE- Architektur, die heterogener Natur ist - der "große" Kern ist schnell und verbraucht viel Energie, und der "kleine" Kern ist langsam und verbraucht weniger. Die Idee ist, dass unbedeutende Aufgaben auf einem kleinen Kern ausgeführt werden können, wodurch Batterie gespart wird. Um die big.LITTLE-Architektur zu unterstützen und die Akkulaufzeit unter Windows 10 unter ARM zu verlängern, wurde dem Scheduler eine heterogene Layoutunterstützung hinzugefügt, die die Wünsche einer Anwendung berücksichtigt, die mit der big.LITTLE-Architektur arbeitet.

Mit Wünschen meine ich, dass Windows versucht, qualitativ hochwertige Dienste für Anwendungen bereitzustellen, Threads zu verfolgen, die im Vordergrund ausgeführt werden (oder denen die Prozessorzeit fehlt), und deren Ausführung auf dem "großen" Kern zu gewährleisten. Alle Hintergrundaufgaben, Dienste und anderen Hilfsthreads werden auf kleinen Kernen ausgeführt. Auch im Programm können Sie die geringe Bedeutung des Threads zwangsweise notieren , damit er auf einem kleinen Kern funktioniert.

Arbeiten für eine andere Person [Im Namen arbeiten]: In Windows wird viel Arbeit im Vordergrund von anderen Diensten ausgeführt, die im Hintergrund arbeiten. Wenn Sie beispielsweise in Outlook suchen, wird die Suche selbst vom Indexer-Hintergrunddienst ausgeführt. Wenn wir nur alle Dienste auf einem kleinen Kern ausführen, leiden die Qualität und Geschwindigkeit der Anwendungen im Vordergrund. Um zu verhindern, dass big.LITTLE-Architekturen in solchen Arbeitsszenarien langsamer werden, überwacht Windows Anwendungsaufrufe, die an andere Prozesse gesendet werden, um die Arbeit in ihrem Namen auszuführen. In diesem Fall geben wir dem Thread, der sich auf den Dienst bezieht, die Priorität im Vordergrund und erzwingen, dass er auf dem großen Kern ausgeführt wird.

Lassen Sie mich diesen ersten Artikel über den Windows-Kernel beenden, der einen Überblick über den Scheduler gibt. Artikel mit ähnlichen technischen Details zur internen Funktionsweise des Betriebssystems werden später folgen.

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


All Articles