Die Linux-Programmierschnittstelle zeigt das Layout eines virtuellen Adressraums eines Prozesses. Ist jede Region im Diagramm ein Segment?
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).
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.
Antworten:
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 :-).
quelle
mov eax, [fs:rdi + 16]
). Der Kernel verwendet GS (afterswapgs
), um den Kernel-Stack des aktuellen Prozesses imsyscall
Einstiegspunkt zu finden. Aber ja, Segmentierung wird nicht als Teil des Hauptspeicherverwaltungs- / Speicherschutzmechanismus des Betriebssystems verwendet.Da der x86 Segmente hat, ist es nicht möglich, sie nicht zu verwenden. Die Basisadressen
cs
(Codesegment) undds
(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.
quelle
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.)
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.
quelle
wrfsbase
ist im geschützten / kompatiblen Modus (nur im langen Modus) unzulässig, sodass ein 32-Bit-Kernelbenutzer-Space die FS-Basis nicht hochsetzen kann.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.
quelle
Dies sind zwei fast völlig unterschiedliche Verwendungen des Wortes "Segment".
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:
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
rep
Anweisungen -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
iret
odersysret
anstelle eines normalen verwendet Sprung- oder Ret-Anweisung.In x86-64 wird der
syscall
Einstiegspunkt verwendetswapgs
, 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). Diesyscall
Anweisung ä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 .quelle