it-swarm.dev

Warum (nicht) Segmentierung?

Ich studiere Betriebssysteme und die x86-Architektur, und während ich über Segmentierung und Paging las, war ich natürlich neugierig, wie moderne Betriebssysteme mit Speicherverwaltung umgehen. Nach dem, was ich fand, meiden Linux und die meisten anderen Betriebssysteme im Wesentlichen die Segmentierung zugunsten von Paging. Einige der Gründe dafür waren Einfachheit und Portabilität.

Welche praktischen Anwendungen gibt es für die Segmentierung (x86 oder anders) und werden wir jemals robuste Betriebssysteme sehen, die sie verwenden, oder werden sie weiterhin ein Paging-basiertes System bevorzugen.

Jetzt weiß ich, dass dies eine geladene Frage ist, aber ich bin gespannt, wie die Segmentierung mit neu entwickelten Betriebssystemen gehandhabt wird. Ist es so sinnvoll, Paging zu bevorzugen, dass niemand einen stärker segmentierten Ansatz in Betracht zieht? Wenn ja warum?


Und wenn ich "meiden" -Segmentierung sage, impliziere ich, dass Linux sie nur so weit verwendet, wie es muss. Nur 4 Segmente für Benutzer- und Kernelcode-/Datensegmente. Beim Lesen der Intel-Dokumentation hatte ich gerade das Gefühl, dass die Segmentierung auf robustere Lösungen ausgelegt ist. Andererseits wurde mir oft gesagt, wie kompliziert das x86 sein kann.


Ich fand diese interessante Anekdote, nachdem ich mit Linux verknüpft war. Torvalds ursprüngliche "Ankündigung" für Linux. Er sagte dies einige Beiträge später:

Ich würde einfach sagen, dass eine Portierung unmöglich ist. Es ist meistens in C, aber die meisten Leute würden nicht nennen, was ich schreibe C. Es verwendet alle denkbaren Funktionen des 386, die ich finden konnte, da es auch ein Projekt war, um mich über den 386 zu unterrichten. Wie bereits erwähnt, verwendet es eine MMU , sowohl für Paging (noch nicht auf Festplatte) als auch für Segmentierung. Es ist die Segmentierung, die es WIRKLICH 386 abhängig macht (jede Aufgabe hat ein 64-MB-Segment für Code und Daten - maximal 64 Aufgaben in 4 GB. Jeder, der mehr als 64 MB/Aufgabe benötigt - harte Cookies).

Ich denke, meine eigenen Experimente mit x86 haben mich dazu gebracht, diese Frage zu stellen. Linus hatte StackOverflow nicht, also hat er es nur implementiert, um es auszuprobieren.

44
Mr. Shickadance

Mit der Segmentierung wäre es beispielsweise möglich, jedes dynamisch zugewiesene Objekt (malloc) in ein eigenes Speichersegment zu stellen. Die Hardware würde die Segmentgrenzen automatisch überprüfen und die gesamte Klasse von Sicherheitslücken (Pufferüberläufe) würde beseitigt.

Da alle Segmentversätze bei Null beginnen, wäre der gesamte kompilierte Code automatisch positionsunabhängig. Das Aufrufen einer anderen DLL würde zu einem Fernaufruf mit konstantem Offset führen (abhängig von der aufgerufenen Funktion). Dies würde Linker und Loader erheblich vereinfachen.

Mit 4 Schutzringen ist es möglich, eine detailliertere Zugriffskontrolle (mit Paging haben Sie nur zwei Schutzstufen: Benutzer und Supervisor) und robustere Betriebssystemkerne zu entwickeln. Beispielsweise hat nur Ring 0 vollen Zugriff auf die Hardware. Durch Aufteilen des Kernel und der Gerätetreiber des Kernbetriebssystems in die Ringe 0 und 1 können Sie ein robusteres und sehr schnelles Mikrokernel-Betriebssystem erstellen, bei dem die meisten relevanten Zugriffsprüfungen von HW durchgeführt werden. (Gerätetreiber können über die E/A-Zugriffsbitmap im TSS auf Hardware zugreifen.)

Allerdings ist x86 etwas eingeschränkt. Es hat nur 4 "freie" Datensegmentregister; Das Nachladen ist ziemlich teuer und es ist möglich, gleichzeitig auf nur 8192 Segmente zuzugreifen. (Angenommen, Sie möchten die Anzahl der zugänglichen Objekte maximieren, sodass der GDT nur Systemdeskriptoren und LDT-Deskriptoren enthält.)

Im 64-Bit-Modus wird die Segmentierung als "Legacy" bezeichnet, und die Überprüfung der Hardwarelimits wird nur unter bestimmten Umständen durchgeführt. IMHO, ein großer Fehler. Eigentlich beschuldige ich Intel nicht, ich beschuldige hauptsächlich Entwickler, von denen die meisten der Meinung waren, dass die Segmentierung "zu kompliziert" sei und sich nach einem flachen Adressraum sehne. Ich beschuldige auch die OS-Autoren, denen die Vorstellungskraft fehlte, die Segmentierung sinnvoll einzusetzen. (AFAIK, OS/2 war das einzige Betriebssystem, das die Segmentierungsfunktionen vollständig nutzte.)

32
zvrba

Die kurze Antwort lautet, dass die Segmentierung ein Hack ist, mit dem ein Prozessor mit einer eingeschränkten Fähigkeit, den Speicher zu adressieren, diese Grenzen überschreitet.

Im Fall des 8086 befanden sich 20 Adressleitungen auf dem Chip, was bedeutet, dass er physisch auf 1 MB Speicher zugreifen konnte. Die interne Architektur basierte jedoch auf einer 16-Bit-Adressierung, wahrscheinlich aufgrund des Wunsches, die Konsistenz mit dem 8080 beizubehalten. Der Befehlssatz enthielt daher Segmentregister, die mit den 16-Bit-Indizes kombiniert wurden, um die Adressierung der gesamten 1 MB Speicher zu ermöglichen . Der 80286 erweiterte dieses Modell um eine echte MMU, um den segmentbasierten Schutz und die Adressierung von mehr Speicher (iirc, 16 MB) zu unterstützen.

Im Fall des PDP-11 stellten spätere Modelle des Prozessors eine Segmentierung in Befehls- und Datenräume bereit, um wiederum die Einschränkungen eines 16-Bit-Adressraums zu unterstützen.

Das Problem bei der Segmentierung ist einfach: Ihr Programm muss die Einschränkungen der Architektur explizit umgehen. Im Fall des 8086 bedeutete dies, dass der größte zusammenhängende Speicherblock, auf den Sie zugreifen konnten, 64 KB betrug. Wenn Sie auf mehr als das zugreifen müssten, müssten Sie Ihre Segmentregister ändern. Für einen C-Programmierer bedeutete dies, dass Sie dem C-Compiler mitteilen mussten, welche Art von Zeigern er generieren sollte.

Es war viel einfacher, den MC68k zu programmieren, der über eine interne 32-Bit-Architektur und einen physischen 24-Bit-Adressraum verfügte.

26
parsifal

Für 80x86 gibt es 4 Optionen: "Nichts", nur Segmentierung, nur Paging sowie Segmentierung und Paging.

Für "nichts" (keine Segmentierung oder Paging) gibt es am Ende keine einfache Möglichkeit, einen Prozess vor sich selbst zu schützen, keine einfache Möglichkeit, Prozesse voreinander zu schützen, keine Möglichkeit, Dinge wie die Fragmentierung des physischen Adressraums zu handhaben, keine Möglichkeit, die Position zu vermeiden unabhängiger Code usw. Trotz all dieser Probleme kann er (theoretisch) in einigen Situationen nützlich sein (z. B. eingebettetes Gerät, auf dem nur eine Anwendung ausgeführt wird, oder etwas, das JIT verwendet und sowieso alles virtualisiert).

Nur zur Segmentierung; Es löst fast das Problem "einen Prozess vor sich selbst schützen", aber es erfordert viele Umgehungen, um ihn nutzbar zu machen, wenn ein Prozess mehr als 8192 Segmente verwenden möchte (unter der Annahme eines LDT pro Prozess), was ihn größtenteils kaputt macht. Sie lösen fast das Problem "Prozesse voreinander schützen". Verschiedene Softwareteile, die auf derselben Berechtigungsstufe ausgeführt werden, können jedoch die Segmente des jeweils anderen laden/verwenden (es gibt Möglichkeiten, dies zu umgehen - Ändern von GDT-Einträgen während Steuerübertragungen und/oder Verwenden von LDTs). Es löst auch meistens das Problem des "positionsunabhängigen Codes" (es kann ein Problem des "segmentabhängigen Codes" verursachen, aber das ist viel weniger bedeutsam). Für das Problem der Fragmentierung des physischen Adressraums wird nichts unternommen.

Nur zum Blättern; Es löst das Problem "Schützen Sie einen Prozess vor sich selbst" nicht sehr (aber seien wir ehrlich, dies ist nur ein Problem beim Debuggen/Testen von Code, der in unsicheren Sprachen geschrieben wurde, und es gibt sowieso viel leistungsfähigere Tools wie valgrind). Es löst das Problem "Prozesse voreinander schützen" vollständig, das Problem "positionsunabhängiger Code" vollständig und das Problem der Fragmentierung des physischen Adressraums vollständig. Als zusätzlichen Bonus eröffnet es einige sehr mächtige Techniken, die ohne Paging nicht annähernd so praktisch sind. Dazu gehören beispielsweise "Copy on Write", Dateien mit Speicherzuordnung, effizientes Swap-Space-Handling usw.

Jetzt würden Sie denken, dass die Verwendung von Segmentierung und Paging die Vorteile von beiden bietet. und theoretisch kann dies, außer dass der einzige Vorteil, den Sie durch die Segmentierung erzielen (der durch Paging nicht besser erreicht wird), eine Lösung für das Problem "Schutz eines Prozesses vor sich selbst" ist, das niemanden wirklich interessiert. In der Praxis erhalten Sie die Komplexität von beiden und den Overhead von beiden, für sehr geringen Nutzen.

Aus diesem Grund verwenden fast alle für 80x86 entwickelten Betriebssysteme keine Segmentierung für die Speicherverwaltung (sie verwenden sie beispielsweise für die Speicherung pro CPU und pro Task, dies dient jedoch hauptsächlich der Bequemlichkeit, um zu vermeiden, dass für diese ein nützlicheres Allzweckregister verwendet wird Dinge).

Natürlich sind CPU-Hersteller nicht albern - sie werden keine Zeit und kein Geld dafür aufwenden, etwas zu optimieren, von dem sie wissen, dass es niemand verwendet (sie werden etwas optimieren, das fast jeder stattdessen verwendet). Aus diesem Grund optimieren CPU-Hersteller die Segmentierung nicht, wodurch die Segmentierung langsamer als möglich wird, sodass Betriebssystementwickler sie noch mehr vermeiden möchten. Meistens wurde die Segmentierung nur aus Gründen der Abwärtskompatibilität beibehalten (was wichtig ist).

Schließlich entwarf AMD den Langmodus. Es gab keinen alten/vorhandenen 64-Bit-Code, über den man sich Sorgen machen musste, sodass AMD (für 64-Bit-Code) so viel Segmentierung wie möglich beseitigte. Dies gab den Betriebssystementwicklern einen weiteren Grund (keine einfache Möglichkeit, Code für die Segmentierung auf 64-Bit zu portieren), die Segmentierung weiterhin zu vermeiden.

16
Brendan

Ich bin ziemlich verblüfft darüber, dass in all der Zeit seit der Veröffentlichung dieser Frage niemand die Ursprünge segmentierter Speicherarchitekturen und die wahre Leistung erwähnt hat, die sie sich leisten können.

Das ursprüngliche System, das alle Merkmale des Entwurfs und der Verwendung von segmentierten ausgelagerten virtuellen Speichersystemen (zusammen mit symmetrischen Mehrfachverarbeitungs- und hierarchischen Dateisystemen) entweder erfand oder in nützliche Form verfeinerte, war Multics (und siehe) auch die Multicians Seite). Durch den segmentierten Speicher kann Multics dem Benutzer eine Ansicht anbieten, dass alles im (virtuellen) Speicher vorhanden ist, und es ermöglicht die ultimative Freigabeebene von alles in direkter Form (dh direkt im Speicher adressierbar). Das Dateisystem wird einfach zu einer Zuordnung zu allen Segmenten im Speicher. Bei ordnungsgemäßer systematischer Verwendung (wie bei Multics) befreit der segmentierte Speicher den Benutzer von den vielen Belastungen bei der Verwaltung des Sekundärspeichers, der gemeinsamen Nutzung von Daten und der Kommunikation zwischen Prozessen. Andere Antworten haben einige handgewellte Behauptungen aufgestellt, dass die Verwendung von segmentiertem Speicher schwieriger ist, aber dies ist einfach nicht wahr, und Multics hat dies vor Jahrzehnten mit großem Erfolg bewiesen.

Intel hat mit dem 80286 eine humpelnde Version des segmentierten Speichers entwickelt, die zwar recht leistungsfähig ist, aufgrund ihrer Einschränkungen jedoch nicht für wirklich nützliche Zwecke verwendet werden kann. Der 80386 verbesserte diese Einschränkungen, aber die damaligen Marktkräfte verhinderten den Erfolg eines Systems, das diese Verbesserungen wirklich nutzen konnte. In den Jahren seitdem scheinen allzu viele Menschen gelernt zu haben, die Lehren aus der Vergangenheit zu ignorieren.

Intel versuchte auch früh, ein leistungsfähigeres Super-Mikro namens iAPX 432 zu entwickeln, das zu dieser Zeit alles andere weit übertroffen hätte, und es hatte eine segmentierte Speicherarchitektur und andere Funktionen, die stark auf objektorientiert ausgerichtet waren Programmierung. Die ursprüngliche Implementierung war jedoch einfach zu langsam, und es wurden keine weiteren Versuche unternommen, dies zu beheben.

Eine detailliertere Diskussion darüber, wie Multics Segmentierung und Paging verwendet, finden Sie in Paul Green's Artikel Multics Virtual Memory - Tutorial and Reflections

14
Greg A. Woods

Die Segmentierung war ein Hack/Workaround, um zu ermöglichen, dass bis zu 1 MB Speicher von einem 16-Bit-Prozessor adressiert werden können - normalerweise wären nur 64 KB Speicher verfügbar gewesen.

Als 32-Bit-Prozessoren hinzukamen, konnten Sie mit einem Flat-Memory-Modell bis zu 4 GB Speicher adressieren, und es war keine Segmentierung mehr erforderlich. - Die Segmentregister wurden im geschützten Modus als Selektoren für GDT/Paging neu bestimmt (obwohl Sie dies können) haben geschützten Modus 16-Bit).

Auch ein Flat-Memory-Modus ist für Compiler weitaus praktischer - Sie können 16-Bit-segmentierte Programme in C schreiben, aber es ist ein bisschen umständlich. Ein Flat-Memory-Modell macht alles einfacher.

6
Justin

Einige Architekturen (wie ARM) unterstützen überhaupt keine Speichersegmente. Wenn Linux quellenabhängig von Segmenten gewesen wäre, hätte es nicht einfach auf diese Architekturen portiert werden können.

Wenn man das Gesamtbild betrachtet, hat das Versagen von Speichersegmenten mit der anhaltenden Popularität von C und Zeigerarithmetik zu tun. Die C-Entwicklung ist in einer Architektur mit flachem Speicher praktischer. und wenn Sie einen flachen Speicher wünschen, wählen Sie Speicher-Paging.

Es gab eine Zeit um die Wende der 80er Jahre, als Intel als Unternehmen die zukünftige Popularität von Ada und anderen übergeordneten Programmiersprachen erwartete. Dies ist im Grunde genommen der Grund für einige ihrer spektakuläreren Fehler, wie die schreckliche Speichersegmentierung von APX432 und 286. Mit dem 386 kapitulierten sie vor Flat-Memory-Programmierern; Paging und ein TLB wurden hinzugefügt und die Segmente wurden auf 4 GB anpassbar gemacht. Und dann entfernte AMD im Grunde genommen Segmente mit x86_64, indem es die Basisregistrierung zu einer Dont-Care/Implied-0 machte (außer für fs? Für TLS, denke ich?).

Die Vorteile von Speichersegmenten liegen jedoch auf der Hand: Sie wechseln die Adressräume, ohne einen TLB neu füllen zu müssen. Vielleicht wird irgendwann jemand eine leistungsfähige CPU herstellen, die Segmentierung unterstützt, wir können ein segmentierungsorientiertes Betriebssystem dafür programmieren, und Programmierer können Ada/Pascal/D/Rust/eine andere Sprache erstellen, die nicht flach ist Speicherprogramme dafür.

5
Ted Middleton

Die Segmentierung ist eine große Belastung für Anwendungsentwickler. Hierher kam der große Push, um die Segmentierung abzuschaffen.

Interessanterweise frage ich mich oft, wie viel besser i86 sein könnte, wenn Intel die gesamte Legacy-Unterstützung für diese alten Modi streifen würde. Hier würde besser eine geringere Leistung und möglicherweise ein schnellerer Betrieb bedeuten.

Ich denke, man könnte argumentieren, dass Intel die Milch mit 16-Bit-Segmenten sauer gemacht hat, was zu einer Art Entwicklerrevolte führte. Aber seien wir ehrlich, ein 64k-Adressraum ist nichts Besonderes, wenn Sie sich eine moderne App ansehen. Am Ende mussten sie etwas tun, weil die Konkurrenz effektiv gegen die Adressraumprobleme von i86 vermarkten konnte und tat.

1
Dave

Segmentierung führt zu langsameren Seitenübersetzungen und Austausch

Aus diesen Gründen wurde die Segmentierung auf x86-64 weitgehend eingestellt.

Der Hauptunterschied zwischen ihnen ist, dass:

  • beim Paging wird der Speicher in Blöcke mit fester Größe aufgeteilt
  • die Segmentierung ermöglicht unterschiedliche Breiten für jeden Block

Während es klüger erscheint, konfigurierbare Segmentbreiten zu haben, ist eine Fragmentierung unvermeidlich, wenn Sie die Speichergröße für einen Prozess erhöhen, z.

|   | process 1 |       | process 2 |                        |
     -----------         -----------
0                                                            max

wird schließlich werden, wenn Prozess 1 wächst:

|   | process 1        || process 2 |                        |
     ------------------  -------------
0                                                            max

bis eine Trennung unvermeidlich ist:

|   | process 1 part 1 || process 2 |   | process 1 part 2 | |
     ------------------  -----------     ------------------
0                                                            max

An diesem Punkt:

  • die einzige Möglichkeit, Seiten zu übersetzen, besteht darin, alle Seiten von Prozess 1 binär zu durchsuchen, wobei ein inakzeptables Protokoll (n) erstellt wird.
  • ein Swap aus Prozess 1 Teil 1 könnte riesig sein, da dieses Segment riesig sein könnte

Bei Seiten mit fester Größe jedoch:

  • jede 32-Bit-Übersetzung führt nur 2 Speicherlesevorgänge durch: Verzeichnis- und Seitentabellenlauf
  • jeder Tausch ist ein akzeptables 4KiB

Speicherblöcke mit fester Größe sind einfach besser zu verwalten und haben das aktuelle Betriebssystemdesign dominiert.

Siehe auch: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work