Wird Kernel-Speicherplatz verwendet, wenn der Kernel im Auftrag des Benutzerprogramms ausgeführt wird, z. B. Systemaufruf? Oder ist es der Adressraum für alle Kernel-Threads (zum Beispiel Scheduler)?
Ja und ja.
Bevor wir weiter gehen, sollten wir dies über die Erinnerung sagen.
Der Speicher wird in zwei Bereiche unterteilt:
- Der Benutzerbereich , dh eine Reihe von Speicherorten, an denen normale Benutzerprozesse ausgeführt werden (dh alles andere als der Kernel). Die Rolle des Kernels besteht darin, Anwendungen, die in diesem Bereich ausgeführt werden, vor dem Durcheinander miteinander und mit dem Computer zu schützen.
- Der Kernel-Space , der der Ort ist, unter dem der Code des Kernels gespeichert und ausgeführt wird.
Prozesse, die unter dem Benutzerbereich ausgeführt werden, haben nur Zugriff auf einen begrenzten Teil des Speichers, während der Kernel auf den gesamten Speicher zugreifen kann. Prozesse, die im Benutzerbereich ausgeführt werden, haben auch keinen Zugriff auf den Kernelbereich. User-Space-Prozesse können nur über eine vom Kernel bereitgestellte Schnittstelle auf einen kleinen Teil des Kernels zugreifen - das System ruft auf . Wenn ein Prozess einen Systemaufruf ausführt, wird ein Software-Interrupt an den Kernel gesendet, der dann den entsprechenden Interrupt-Handler auslöst und seine Arbeit fortsetzt, nachdem der Handler fertig ist.
Kernel-Space-Code hat die Eigenschaft, im "Kernel-Modus" ausgeführt zu werden. Dies ist (auf Ihrem typischen Desktop-x86-Computer) der Code, der unter Ring 0 ausgeführt wird . In der x86-Architektur gibt es normalerweise 4 Schutzringe . Ring 0 (Kernel-Modus), Ring 1 (kann von Hypervisoren oder Treibern für virtuelle Maschinen verwendet werden), Ring 2 (kann von Treibern verwendet werden, da bin ich mir allerdings nicht so sicher). Unter Ring 3 laufen typische Anwendungen. Es ist der Ring mit den geringsten Berechtigungen, und Anwendungen, die darauf ausgeführt werden, haben Zugriff auf eine Teilmenge der Anweisungen des Prozessors. Ring 0 (Kernel Space) ist der am meisten privilegierte Ring und hat Zugriff auf alle Anweisungen der Maschine. Beispielsweise kann eine "einfache" Anwendung (wie ein Browser) keine x86-Assembly-Anweisungen verwendenlgdt
um die globale Deskriptortabelle zu laden oder hlt
einen Prozessor anzuhalten.
Wenn es das erste ist, bedeutet dies, dass das normale Anwenderprogramm nicht mehr als 3 GB Speicher haben kann (wenn die Unterteilung 3 GB + 1 GB beträgt)? Wie kann in diesem Fall der Kernel den hohen Speicher verwenden, da auf welche Adresse des virtuellen Speichers die Seiten aus dem hohen Speicher abgebildet werden, da 1 GB Kernelspeicher logisch abgebildet werden?
Eine Antwort auf diese Frage finden Sie in der hervorragenden Antwort von wag hier
-1
für Hypervisoren? en.wikipedia.org/wiki/Protection_ringCPU-Ringe sind die deutlichste Unterscheidung
Im x86-geschützten Modus befindet sich die CPU immer in einem von 4 Ringen. Der Linux-Kernel verwendet nur 0 und 3:
Dies ist die härteste und schnellste Definition von Kernel vs Userland.
Warum Linux die Ringe 1 und 2 nicht verwendet: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
Wie wird der aktuelle Ring ermittelt?
Der aktuelle Ring wird ausgewählt durch eine Kombination von:
Globale Deskriptortabelle: Eine speicherinterne Tabelle mit GDT-Einträgen, und jeder Eintrag verfügt über ein Feld
Privl
, das den Ring codiert.Der LGDT-Befehl setzt die Adresse auf die aktuelle Deskriptortabelle.
Siehe auch: http://wiki.osdev.org/Global_Descriptor_Table
Die Segmentregister CS, DS usw. zeigen auf den Index eines Eintrags in der GDT.
Beispielsweise
CS = 0
bedeutet dies , dass der erste Eintrag der GDT derzeit für den ausführenden Code aktiv ist.Was kann jeder Ring?
Der CPU-Chip ist physisch so aufgebaut, dass:
Ring 0 kann alles
Ring 3 kann nicht mehrere Befehle ausführen und in mehrere Register schreiben, insbesondere:
kann seinen eigenen Ring nicht ändern! Andernfalls könnte es sich selbst auf Ring 0 setzen und Ringe wären nutzlos.
Mit anderen Worten, kann den aktuellen Segmentdeskriptor , der den aktuellen Ring bestimmt , nicht ändern .
Die Seitentabellen können nicht geändert werden: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
Mit anderen Worten, das CR3-Register kann nicht geändert werden, und das Paging selbst verhindert die Änderung der Seitentabellen.
Dies verhindert, dass ein Prozess den Speicher anderer Prozesse sieht, um die Sicherheit und die Programmierung zu vereinfachen.
Interrupt-Handler können nicht registriert werden. Diese werden durch Schreiben in Speicherstellen konfiguriert, was auch durch Paging verhindert wird.
Handler werden in Ring 0 ausgeführt und würden das Sicherheitsmodell beschädigen.
Mit anderen Worten, kann die LGDT- und LIDT-Anweisungen nicht verwenden.
Ich kann keine E / A-Anweisungen wie
in
und ausführenout
und habe daher willkürliche Hardwarezugriffe.Andernfalls wären beispielsweise Dateiberechtigungen unbrauchbar, wenn ein Programm direkt von der Festplatte lesen könnte.
Genauer gesagt dank Michael Petch : Es ist dem Betriebssystem tatsächlich möglich, E / A-Anweisungen auf Ring 3 zuzulassen, dies wird tatsächlich vom Task- Statussegment gesteuert .
Was nicht möglich ist, ist, dass Ring 3 sich die Erlaubnis dazu gibt, wenn er es überhaupt nicht hat.
Linux lässt es immer nicht zu. Siehe auch: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss
Wie wechseln Programme und Betriebssysteme zwischen Ringen?
Wenn die CPU eingeschaltet ist, startet sie das Startprogramm in Ring 0 (na ja, aber es ist eine gute Annäherung). Sie können sich dieses ursprüngliche Programm als den Kernel vorstellen (aber normalerweise ist es ein Bootloader, der den Kernel dann noch in Ring 0 aufruft).
Wenn ein Userland-Prozess den Kernel dazu auffordert, in eine Datei zu schreiben, verwendet er eine Anweisung, die einen Interrupt erzeugt, z. B.
int 0x80
odersyscall
um dem Kernel ein Signal zu geben. x86-64 Linux syscall hallo welt beispiel:kompilieren und ausführen:
GitHub Upstream .
In diesem Fall ruft die CPU einen Interrupt-Callback-Handler auf, den der Kernel beim Booten registriert hat. Hier ist ein konkretes Baremetal-Beispiel, das einen Handler registriert und verwendet .
Dieser Handler wird in Ring 0 ausgeführt, der entscheidet, ob der Kernel diese Aktion zulässt, die Aktion ausführt und das Userland-Programm in Ring 3 neu startet. X86_64
Wenn der
exec
Systemaufruf verwendet wird (oder wenn der Kernel startet/init
), bereitet der Kernel die Register und den Speicher des neuen Userland-Prozesses vor, springt zum Einstiegspunkt und schaltet die CPU auf Ring 3 umWenn das Programm versucht, in ein verbotenes Register oder eine verbotene Speicheradresse zu schreiben (wegen Paging), ruft die CPU auch einen Kernel-Callback-Handler in Ring 0 auf.
Aber da das Userland ungezogen war, könnte der Kernel den Prozess dieses Mal abbrechen oder ihm eine Warnung mit einem Signal geben.
Beim Booten des Kernels wird eine Hardware-Uhr mit einer festen Frequenz eingerichtet, die regelmäßig Interrupts generiert.
Diese Hardware-Uhr generiert Interrupts, die Ring 0 ausführen, und ermöglicht es ihr, zu planen, welche Userland-Prozesse aufgeweckt werden sollen.
Auf diese Weise kann die Planung auch dann erfolgen, wenn die Prozesse keine Systemaufrufe ausführen.
Was bringt es, mehrere Ringe zu haben?
Die Trennung von Kernel und Userland bietet zwei wesentliche Vorteile:
Wie spielt man damit herum?
Ich habe ein Bare-Metal-Setup erstellt, mit dem Ringe direkt bearbeitet werden können: https://github.com/cirosantilli/x86-bare-metal-examples
Ich hatte leider nicht die Geduld, ein Userland-Beispiel zu erstellen, aber ich ging so weit wie das Paging-Setup, sodass Userland machbar sein sollte. Ich würde gerne eine Pull-Anfrage sehen.
Alternativ können Linux-Kernelmodule in Ring 0 ausgeführt werden, sodass Sie damit privilegierte Operationen ausprobieren können, z. B. die Kontrollregister lesen: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-from-a-program-getting-segmenta / 7419306 # 7419306
Hier ist ein praktisches QEMU + Buildroot-Setup, mit dem Sie es ausprobieren können, ohne Ihren Host zu töten.
Der Nachteil von Kernel-Modulen ist, dass andere kthreads ausgeführt werden und Ihre Experimente stören können. Aber theoretisch können Sie alle Interrupt-Handler mit Ihrem Kernel-Modul übernehmen und das System besitzen, das wäre eigentlich ein interessantes Projekt.
Negative Ringe
Während negative Ringe im Intel-Handbuch eigentlich nicht erwähnt werden, gibt es CPU-Modi, die über weitere Funktionen als Ring 0 selbst verfügen und daher gut zum Namen "negativer Ring" passen.
Ein Beispiel ist der Hypervisor-Modus, der bei der Virtualisierung verwendet wird.
Weitere Informationen finden Sie unter: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
ARM
In ARM werden die Ringe stattdessen als Ausnahmestufen bezeichnet, die Hauptideen bleiben jedoch dieselben.
In ARMv8 gibt es 4 Ausnahmestufen, die üblicherweise verwendet werden als:
EL0: userland
EL1: Kernel ("Supervisor" in der ARM-Terminologie).
Wird mit der
svc
Anweisung (SuperVisor-Aufruf) eingegeben , die zuvor alsswi
Unified Assembly bezeichnet wurde. Dies ist die Anweisung, die zum Ausführen von Linux-Systemaufrufen verwendet wird. Hallo Welt ARMv8 Beispiel:GitHub Upstream .
Testen Sie es mit QEMU unter Ubuntu 16.04:
Hier ist ein konkretes Baremetal-Beispiel, das einen SVC-Handler registriert und einen SVC-Aufruf ausführt .
EL2: Hypervisoren , zum Beispiel Xen .
Eingegeben mit der
hvc
Anweisung (HyperVisor Call).Ein Hypervisor ist für ein Betriebssystem, ein Betriebssystem für das Benutzerland.
Mit Xen können Sie beispielsweise mehrere Betriebssysteme wie Linux oder Windows gleichzeitig auf demselben System ausführen und die Betriebssysteme aus Sicherheitsgründen und zur Vereinfachung des Debuggens voneinander isolieren, genau wie dies Linux für Userland-Programme tut.
Hypervisoren sind ein wesentlicher Bestandteil der heutigen Cloud-Infrastruktur: Sie ermöglichen die Ausführung mehrerer Server auf einer einzigen Hardware, halten die Hardwarenutzung stets nahe bei 100% und sparen viel Geld.
AWS verwendete Xen zum Beispiel bis 2017, als der Wechsel zu KVM die Nachricht verbreitete .
EL3: noch ein Level. TODO-Beispiel.
Eingetragen mit der
smc
Anweisung (Secure Mode Call)Das ARMv8-Architekturreferenzmodell DDI 0487C.a - Kapitel D1 - Das Modell des Programmierers auf Systemebene AArch64 - Abbildung D1-1 veranschaulicht dies auf hervorragende Weise:
Beachten Sie, dass ARM, möglicherweise aufgrund der Vorteile im Nachhinein, eine bessere Namenskonvention für die Berechtigungsstufen als x86 hat, ohne dass negative Stufen erforderlich sind: 0 ist die niedrigere und 3 die höchste. Höhere Ebenen werden in der Regel häufiger erstellt als niedrigere.
Die aktuelle EL kann mit der
MRS
Anweisung abgefragt werden : https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etcFür ARM müssen nicht alle Ausnahmebedingungen vorhanden sein, um Implementierungen zu ermöglichen, bei denen die Funktion zum Einsparen von Chipfläche nicht erforderlich ist. ARMv8 "Ausnahmestufen" sagt:
QEMU ist beispielsweise standardmäßig EL1, EL2 und EL3 können jedoch mit den folgenden Befehlszeilenoptionen aktiviert werden: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up
Code-Schnipsel getestet auf Ubuntu 18.10.
quelle
Ja, dies ist auf einem normalen Linux-System der Fall. An einer Stelle gab es eine Reihe von "4G / 4G" -Patches, die den Benutzer- und den Kernel-Adressraum völlig unabhängig machten (zu Performance-Lasten, da es dem Kernel erschwert wurde, auf den Benutzerspeicher zuzugreifen), aber ich glaube nicht Sie wurden jemals flussaufwärts zusammengeführt und das Interesse schwand mit dem Aufstieg von x86-64
Die Art und Weise, wie Linux früher funktionierte (und auf Systemen, auf denen der Speicher im Vergleich zum Adressraum klein ist), bestand darin, dass der gesamte physische Speicher permanent dem Kernel-Teil des Adressraums zugeordnet wurde. Auf diese Weise konnte der Kernel ohne erneute Zuordnung auf den gesamten physischen Speicher zugreifen, aber er lässt sich offensichtlich nicht auf 32-Bit-Computer mit viel physischem Speicher skalieren.
So wurde das Konzept des niedrigen und hohen Gedächtnisses geboren. Der "niedrige" Speicher wird permanent in den Adressraum des Kernels abgebildet. "Hoch" ist der Speicher nicht.
Wenn der Prozessor einen Systemaufruf ausführt, wird er im Kernelmodus ausgeführt, befindet sich jedoch immer noch im Kontext des aktuellen Prozesses. Auf diese Weise kann direkt auf den Kernel-Adressraum und den Benutzer-Adressraum des aktuellen Prozesses zugegriffen werden (vorausgesetzt, Sie verwenden die oben genannten 4G / 4G-Patches nicht). Dies bedeutet, dass es kein Problem ist, einem Userland-Prozess "hohen" Speicher zuzuweisen.
Die Verwendung von "hohem" Speicher für Kernelzwecke ist eher ein Problem. Um auf hohen Speicher zuzugreifen, der nicht dem aktuellen Prozess zugeordnet ist, muss er temporär dem Adressraum des Kernels zugeordnet werden. Das bedeutet zusätzlichen Code und eine Leistungsstrafe.
quelle