Verwendet Linux keine Segmentierung, sondern nur Paging?

24

Die Linux-Programmierschnittstelle zeigt das Layout eines virtuellen Adressraums eines Prozesses. Ist jede Region im Diagramm ein Segment?

Bildbeschreibung hier eingeben

Aus dem Verständnis des Linux-Kernels ,

Ist es richtig, dass das Folgende bedeutet, dass die Segmentierungseinheit in der MMU die Segmente und Offsets innerhalb der Segmente der Adresse des virtuellen Speichers zuordnet und die Paging-Einheit dann die Adresse des virtuellen Speichers der Adresse des physischen Speichers zuordnet?

Die Speicherverwaltungseinheit (MMU) wandelt eine logische Adresse mittels einer als Segmentierungseinheit bezeichneten Hardwareschaltung in eine lineare Adresse um; Anschließend wandelt eine zweite Hardwareschaltung, die als Paging-Einheit bezeichnet wird, die lineare Adresse in eine physikalische Adresse um (siehe Abbildung 2-1).

Bildbeschreibung hier eingeben

Warum heißt es dann, dass Linux keine Segmentierung, sondern nur Paging verwendet?

In 80x86-Mikroprozessoren wurde die Segmentierung integriert, um Programmierer dazu zu ermutigen, ihre Anwendungen in logisch zusammengehörige Einheiten wie Subroutinen oder globale und lokale Datenbereiche aufzuteilen. Allerdings Linux verwendet Segmentierung in sehr begrenztem Umfang. Tatsächlich sind Segmentierung und Paging etwas redundant, da beide verwendet werden können, um die physikalischen Adressräume von Prozessen zu trennen: Die Segmentierung kann jedem Prozess einen unterschiedlichen linearen Adressraum zuweisen, während Paging denselben linearen Adressraum in unterschiedliche physikalische Adressräume abbilden kann . Linux zieht aus folgenden Gründen das Paging der Segmentierung vor:

• Die Speicherverwaltung ist einfacher, wenn alle Prozesse dieselben Segmentregisterwerte verwenden, dh wenn sie denselben Satz linearer Adressen verwenden.

• Eines der Designziele von Linux ist die Portierbarkeit auf eine Vielzahl von Architekturen. Insbesondere RISC-Architekturen unterstützen die Segmentierung nur eingeschränkt.

Die 2.6-Version von Linux verwendet die Segmentierung nur, wenn dies für die 80x86-Architektur erforderlich ist.

Tim
quelle
Können Sie bitte die Editionen angeben. Es kann auch hilfreich sein, Autorennamen anzugeben. Ich weiß zumindest, dass der erste von einer prominenten Figur stammt. Die beiden Titelnamen sind jedoch etwas allgemein gehalten, mir war zunächst nicht klar, dass es sich um Bücher handelt :-).
Sourcejedi
2
Zu "80x86-Mikroprozessoren wurden segmentiert ...": Das stimmt einfach nicht. Es ist ein Erbe der 808x-Prozessoren, die über 16-Bit-Datenzeiger und 64-KB-Speichersegmente verfügten. Mit Segmentzeigern konnten Sie Segmente wechseln, um mehr Speicher zu adressieren. Diese Architektur wurde auf 80x86 übertragen (wobei die Zeigergröße auf 33 Bit erhöht wurde). Heutzutage gibt es im x86_64-Modell 64-Bit-Zeiger, die (theoretisch - ich denke, es werden nur 48 Bit verwendet) 16 Exabyte adressieren können, sodass Segmente nicht erforderlich sind.
Jamesqf
2
@jamesqf, der 32-Bit-geschützte Modus in der 386 unterstützt Segmente, die sich deutlich von den 16-Byte-skalierten Zeigern in der 8086 unterscheiden. Das soll natürlich nichts über ihre Nützlichkeit aussagen.
ilkkachu
@jamesqf 80186 hatte das gleiche Speichermodell wie 8086, keine "33 Bits"
Jasen
Keine Antwort, daher nur ein Kommentar: Segmente und Seiten sind nur im Kontext des Austauschs vergleichbar (z. B. Seitenaustausch gegen Segmentaustausch), und in diesem Kontext bläst der Seitenaustausch den Segmentaustausch einfach aus dem Wasser. Wenn Sie Segmente ein- oder auslagern , müssen Sie das gesamte Segment austauschen. Dies können 2-4 GB sein. Dies war noch nie eine echte Sache für x86. Mit Seiten können Sie immer an 4KB-Einheiten arbeiten. Wenn es darum geht, auf Speicher zuzugreifen, werden Segmente und Seiten durch Seitentabellen in Beziehung gesetzt, und ein Vergleich wäre mit Orangen vergleichbar.
Vhu

Antworten:

20

Die x86-64-Architektur verwendet im langen Modus (64-Bit-Modus) keine Segmentierung.

Vier der Segmentregister: CS, SS, DS und ES werden auf 0 und die Grenze auf 2 ^ 64 gesetzt.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

Das OS kann nicht mehr einschränken, welche Bereiche der "linearen Adressen" zur Verfügung stehen. Daher kann die Segmentierung nicht für den Speicherschutz verwendet werden. es muss ganz auf Paging angewiesen sein.

Machen Sie sich keine Sorgen über die Details von x86-CPUs, die nur in älteren 32-Bit-Modi verwendet werden können. Linux für die 32-Bit-Modi wird nicht so häufig verwendet. Es kann sogar als "in einem Zustand der gütigen Vernachlässigung für mehrere Jahre" betrachtet werden. Siehe 32-Bit x86-Unterstützung in Fedora [LWN.net, 2017].

(Es kommt vor, dass 32-Bit-Linux auch keine Segmentierung verwendet. Aber Sie müssen mir nicht vertrauen, Sie können es einfach ignorieren :-).

sourcejedi
quelle
Das ist ein bisschen übertrieben. base / limit sind im Long-Modus für die alten Original-8086-Segmente (CS / DS / ES / SS) auf 0 / -1 festgelegt, aber FS und GS haben immer noch eine willkürliche Segmentbasis. Der in CS geladene Segmentdeskriptor bestimmt, ob die CPU im 32- oder 64-Bit-Modus ausgeführt wird. Und User-Space unter x86-64 Linux verwendet FS für den thread-lokalen Speicher ( mov eax, [fs:rdi + 16]). Der Kernel verwendet GS (after swapgs), um den Kernel-Stack des aktuellen Prozesses im syscallEinstiegspunkt zu finden. Aber ja, Segmentierung wird nicht als Teil des Hauptspeicherverwaltungs- / Speicherschutzmechanismus des Betriebssystems verwendet.
Peter Cordes
Dies ist im Grunde genommen das Zitat in der Frage "Die 2.6-Version von Linux verwendet Segmentierung nur, wenn dies für die 80x86-Architektur erforderlich ist." Aber dein 2. Absatz ist grundsätzlich falsch. Linux verwendet die Segmentierung im 32- und 64-Bit-Modus grundsätzlich identisch. dh base = 0 / limit = 2 ^ 32 oder 2 ^ 64 für die klassischen Segmente (CS / DS / ES / SS), die implizit von normalen Befehlen verwendet werden. Im 32-Bit-Linux-Code gibt es nichts, worüber man sich Sorgen machen müsste. die HW-Funktionalität wird dort aber nicht genutzt.
Peter Cordes
@PeterCordes du interpretierst die Antwort grundsätzlich falsch :-). Also habe ich es bearbeitet, um meine Argumentation weniger zweideutig zu machen.
Sourcejedi
Gute Verbesserung, jetzt ist es nicht irreführend. Ich stimme vollkommen mit Ihrem eigentlichen Punkt überein, nämlich dass Sie die x86-Segmentierung vollständig ignorieren können und sollten, da sie nur für OSDEV-Systemverwaltungszwecke und für TLS verwendet wird. Wenn Sie irgendwann mehr darüber erfahren möchten, ist dies viel einfacher zu verstehen, nachdem Sie x86 asm bereits mit einem flachen Speichermodell verstanden haben.
Peter Cordes
8

Da der x86 Segmente hat, ist es nicht möglich, sie nicht zu verwenden. Die Basisadressen cs(Codesegment) und ds(Datensegment) werden jedoch auf Null gesetzt, sodass die Segmentierung nicht wirklich verwendet wird. Eine Ausnahme bilden lokale Thread-Daten. Eines der normalerweise nicht verwendeten Segmentregister zeigt auf lokale Thread-Daten. Dies geschieht jedoch hauptsächlich, um zu vermeiden, dass eines der Allzweckregister für diese Aufgabe reserviert wird.

Es heißt nicht, dass Linux auf dem x86 keine Segmentierung verwendet, da dies nicht möglich wäre. Sie haben bereits einen Teil hervorgehoben: Linux verwendet die Segmentierung in sehr begrenztem Umfang . Der zweite Teil ist, dass Linux die Segmentierung nur verwendet, wenn dies für die 80x86-Architektur erforderlich ist

Sie haben bereits die Gründe angeführt, das Paging ist einfacher und portabler.

RalfFriedl
quelle
7

Ist jede Region im Diagramm ein Segment?

Nein.

Während das Segmentierungssystem (im geschützten 32-Bit-Modus auf einem x86-System) separate Code-, Daten- und Stapelsegmente unterstützt, werden in der Praxis alle Segmente auf denselben Speicherbereich festgelegt. Das heißt, sie beginnen bei 0 und enden am Ende des Speichers (*) . Das macht die logischen Adressen und linearen Adressen gleich.

Dies wird als "flaches" Speichermodell bezeichnet und ist etwas einfacher als das Modell, in dem Sie unterschiedliche Segmente und dann Zeiger verwenden. Insbesondere benötigt ein segmentiertes Modell längere Zeiger, da der Segmentselektor zusätzlich zum Versatzzeiger enthalten sein muss. (16-Bit-Segmentauswahl + 32-Bit-Versatz für insgesamt 48-Bit-Zeiger; im Gegensatz zu nur einem 32-Bit-flachen Zeiger.)

Der 64-Bit-Modus unterstützt nicht einmal die Segmentierung, sondern nur das flache Speichermodell.

Wenn Sie auf dem 286 im geschützten 16-Bit-Modus programmieren, benötigen Sie mehr Segmente, da der Adressraum 24 Bit beträgt, die Zeiger jedoch nur 16 Bit.

(* Beachten Sie, dass ich mich nicht erinnern kann, wie 32-Bit-Linux mit der Trennung von Kernel und Userspace umgeht. Die Segmentierung würde dies durch Festlegen der Obergrenzen für Userspace-Segmente ermöglichen, sodass der Kernel-Speicherplatz nicht berücksichtigt wird pro Seite Schutzstufe.)

Warum heißt es dann, dass Linux keine Segmentierung, sondern nur Paging verwendet?

Der x86 hat immer noch die Segmente und Sie können sie nicht deaktivieren. Sie werden nur so wenig wie möglich benutzt. Im geschützten 32-Bit-Modus müssen die Segmente für das flache Modell eingerichtet werden, und selbst im 64-Bit-Modus sind sie noch vorhanden.

ilkkachu
quelle
Ich schätze, ein 32-Bit-Kernel könnte Meltdown möglicherweise günstiger abmildern als das Ändern von Seitentabellen, indem Segmentgrenzen für CS / DS / ES / SS festgelegt werden, die verhindern, dass Benutzer über 2G oder 3G auf Speicherplatz zugreifen. (Die Meltdown-Vuln ist eine Problemumgehung für das Kernel / User-Bit in Seitentabelleneinträgen, sodass der User-Space von Seiten lesen kann, die nur dem Kernel zugeordnet sind.) Die VDSO-Seiten werden möglicherweise am oberen Rand des 4G zugeordnet: / wrfsbaseist im geschützten / kompatiblen Modus (nur im langen Modus) unzulässig, sodass ein 32-Bit-Kernelbenutzer-Space die FS-Basis nicht hochsetzen kann.
Peter Cordes
Auf einem 64-Bit-Kernel kann der 32-Bit-Benutzerraum möglicherweise weit zu einem 64-Bit-Codesegment springen, sodass Sie sich beim Meltdown-Schutz nicht auf Segmentbeschränkungen verlassen können, sondern nur auf einen reinen 32-Bit-Kernel. (Das hat große Nachteile auf Computern mit viel physischem RAM, z. B. wenig Speicher für Thread-Stapel.) Auf jeden Fall schützt Linux den Kernel-Speicher mit Paging und belässt base / limit = 0 / -1 im User-Space für den Normalen Segmente (nicht FS / GS, die für die thread-lokale Speicherung verwendet werden).
Peter Cordes
Bevor das NX-Bit in Hardware-Seitentabellen (Hardware Page Tables, PAE) unterstützt wurde, verwendeten einige frühe Sicherheitspatches Segmentierung, um nicht ausführbare Stapel für Benutzer-Space-Code zu erstellen. zB linux.com/news/exec-shield-new-linux-security-feature (Ingo Molnars Post erwähnt "Solar Designers exzellenten" Non-Exec-Stack-Patch ".)
Peter Cordes
3

Linux x86 / 32 verwendet keine Segmentierung in dem Sinne, dass alle Segmente auf dieselbe lineare Adresse und Beschränkung initialisiert werden. x86-Architektur erfordert, dass das Programm Segmente hat: Code kann nur vom Codesegment ausgeführt werden, Stapel kann nur im Stapelsegment lokalisiert werden, Daten können nur in einem der Datensegmente bearbeitet werden. Linux umgeht diesen Mechanismus, indem alle Segmente auf die gleiche Weise festgelegt werden (mit Ausnahmen, die in Ihrem Buch ohnehin nicht erwähnt werden), sodass in jedem Segment dieselbe logische Adresse gültig ist. Dies ist in der Tat gleichbedeutend damit, überhaupt keine Segmente zu haben.

Dmitry Grigoryev
quelle
2

Ist jede Region im Diagramm ein Segment?

Dies sind zwei fast völlig unterschiedliche Verwendungen des Wortes "Segment".

  • x86 Segmentation / Segmentregister: modernes x86 OSes verwenden , um ein flaches Speichermodell , bei dem alle Segmente die gleiche Basis haben = 0 und limit = max in 32-Bit - Modus das gleiche ist wie Hardware erzwingt , daß in 64-Bit - Modus , vestigial macht Segmentierungs Art von . (Mit Ausnahme von FS oder GS, die auch im 64-Bit-Modus für die Thread-lokale Speicherung verwendet werden.)
  • Linker / Program-Loader Abschnitte / Segmente. ( Was ist der Unterschied zwischen Abschnitt und Segment im ELF-Dateiformat? )

Die Verbräuche hat einen gemeinsamen Ursprung: Wenn Sie wurden ein segmentierten Speicher - Modell (insbesondere ohne ausgelagertem virtuellen Speicher), könnten Sie Daten und BSS - Adressen das DS Segmentbasis, Stapel in Bezug auf die SS Basis und Code in Bezug auf dem relativen sein CS-Basisadresse.

So können mehrere unterschiedliche Programme auf unterschiedliche lineare Adressen geladen oder sogar nach dem Start verschoben werden, ohne die 16- oder 32-Bit-Offsets relativ zu den Segmentbasen zu ändern.

Aber dann muss man wissen, zu welchem ​​Segment ein Zeiger relativ ist, also hat man "Fernzeiger" und so weiter. (Tatsächliche 16-Bit-x86-Programme mussten häufig nicht auf ihren Code als Daten zugreifen, sodass sie irgendwo ein 64-KByte-Codesegment und möglicherweise einen weiteren 64-KByte-Block mit DS = SS verwenden konnten die Unterseite (oder ein winziges Codemodell, bei dem alle Segmentbasen gleich sind).


Wie die x86-Segmentierung mit dem Paging interagiert

Die Adresszuordnung im 32/64-Bit-Modus lautet:

  1. segment: offset (Segmentbasis, die durch das Register impliziert wird, das den Offset enthält, oder mit einem Befehlspräfix überschrieben wird)
  2. 32 oder 64-Bit lineare virtuelle Adresse = Basis + Offset. (In einem flachen Speichermodell, wie es Linux verwendet, sind Zeiger / Offsets ebenfalls lineare Adressen. Außer beim Zugriff auf TLS relativ zu FS oder GS.)
  3. Seitentabellen (zwischengespeichert durch TLB) werden linear auf 32 (Legacy-Modus), 36 (Legacy-PAE) oder 52-Bit-Adressen (x86-64) abgebildet. ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

    Dieser Schritt ist optional: Das Paging muss während des Startvorgangs durch Setzen eines Bits in einem Steuerregister aktiviert werden. Ohne Paging sind lineare Adressen physikalische Adressen.

Beachten Sie, dass Sie bei der Segmentierung nicht mehr als 32 oder 64 Bit des virtuellen Adressraums in einem einzelnen Prozess (oder Thread) verwenden können , da der flache (lineare) Adressraum, in den alles abgebildet wird, nur die gleiche Anzahl von Bits aufweist wie die Offsets selbst. (Dies war bei 16-Bit x86 nicht der Fall, bei dem die Segmentierung tatsächlich nützlich war, um mehr als 64 KB Speicher mit überwiegend 16-Bit-Registern und Offsets zu verwenden.)


Die CPU speichert Segmentdeskriptoren, die von der GDT (oder LDT) geladen wurden, einschließlich der Segmentbasis. Wenn Sie einen Zeiger dereferenzieren, wird, abhängig davon, in welchem ​​Register er sich befindet, standardmäßig DS oder SS als Segment verwendet. Der Registerwert (Zeiger) wird als Versatz von der Segmentbasis behandelt.

Da die Segmentbasis normalerweise Null ist, machen CPUs dies in Sonderfällen. Oder aus einer anderen Perspektive, wenn Sie tun eine Basis nicht-Null - Segment haben, Lasten zusätzliche Latenz , da der „besondere“ (normal) Fall der Umgehung Zugabe der Basisadresse nicht gilt.


So richtet Linux x86-Segmentregister ein:

Die Basis und das Limit von CS / DS / ES / SS sind alle 0 / -1 im 32- und 64-Bit-Modus. Dies wird als flaches Speichermodell bezeichnet, da alle Zeiger auf denselben Adressraum verweisen.

(AMD-CPU-Architekten haben die Segmentierung neutralisiert, indem sie ein flaches Speichermodell für den 64-Bit-Modus erzwungen haben, da die Mainstream-Betriebssysteme es ohnehin nicht verwendeten, mit Ausnahme des No-Exec-Schutzes, der durch Paging mit PAE oder x86- 64 Seitentabellenformat.)

  • TLS (Thread Local Storage): FS und GS sind im Langmodus nicht auf base = 0 festgelegt. (Sie waren mit 386 neu und wurden von keiner Anweisung implizit verwendet, auch nicht von den repAnweisungen -string, die ES verwenden.) x86-64 Linux setzt die FS-Basisadresse für jeden Thread auf die Adresse des TLS-Blocks.

    mov eax, [fs: 16]Lädt z. B. einen 32-Bit-Wert aus 16 Bytes in den TLS-Block für diesen Thread.

  • Der CS-Segment-Deskriptor wählt aus, in welchem ​​Modus sich die CPU befindet (16/32/64-Bit-Protected-Modus / Long-Modus). Linux verwendet einen einzelnen GDT-Eintrag für alle 64-Bit-User-Space-Prozesse und einen weiteren GDT-Eintrag für alle 32-Bit-User-Space-Prozesse. (Damit die CPU richtig funktioniert, müssen DS / ES und SS ebenfalls auf gültige Einträge gesetzt sein.) Es wird auch die Berechtigungsstufe (Kernel (Ring 0) vs. Benutzer (Ring 3)) ausgewählt, sodass der Kernel auch dann, wenn er zum 64-Bit-Benutzerraum zurückkehrt, dafür sorgen muss, dass CS sich ändert, indem er iretoder sysretanstelle eines normalen verwendet Sprung- oder Ret-Anweisung.

  • In x86-64 wird der syscallEinstiegspunkt verwendet swapgs, um GS vom GS des Benutzerbereichs zum Kernel zu spiegeln, mit dem der Kernelstapel für diesen Thread ermittelt wird. (Ein spezieller Fall von Thread-lokalem Speicher). Die syscallAnweisung ändert den Stapelzeiger nicht so, dass er auf den Kernelstapel zeigt. Es zeigt immer noch auf den Benutzerstapel, wenn der Kernel den Einstiegspunkt 1 erreicht .

  • DS / ES / SS müssen ebenfalls auf gültige Segmentdeskriptoren eingestellt sein, damit die CPU im geschützten Modus / langen Modus arbeitet, obwohl die Basis / das Limit dieser Deskriptoren im langen Modus ignoriert werden.

Grundsätzlich wird die x86-Segmentierung für TLS und für das obligatorische x86-osdev-Zeug verwendet, das von der Hardware verlangt wird.


Fußnote 1: Spaßgeschichte: Es gibt Mailinglisten-Archive mit Nachrichten zwischen Kernel-Entwicklern und AMD-Architekten, die einige Jahre vor der Veröffentlichung von AMD64-Silizium erstellt wurden syscall. Weitere Informationen finden Sie unter den Links in dieser Antwort .

Peter Cordes
quelle