Ich beschäftige mich mit Betriebssystemen und der x86-Architektur, und während ich über Segmentierung und Paging las, war ich natürlich neugierig, wie moderne Betriebssysteme mit Speicherverwaltung umgehen. Nach meinem Dafürhalten meiden Linux und die meisten anderen Betriebssysteme im Wesentlichen die Segmentierung zugunsten von Paging. Einige der Gründe, die ich dafür fand, waren Einfachheit und Portabilität.
Welche praktischen Anwendungen gibt es für die Segmentierung (x86 oder anders) und werden wir jemals sehen, dass robuste Betriebssysteme 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. Macht es so viel Sinn, Paging zu bevorzugen, dass niemand über einen "segmentierten" Ansatz nachdenken wird? Wenn ja warum?
Und wenn ich Segmentierung meide, impliziere ich, dass Linux sie nur so weit nutzt, 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 mit Blick auf robustere Lösungen entwickelt wurde. Andererseits wurde mir bei vielen Gelegenheiten gesagt, wie überkompliziert das x86 sein kann.
Ich fand diese interessante Anekdote, nachdem ich mit Torvalds ursprünglicher Ankündigung für Linux verlinkt war. Er sagte dies ein paar Beiträge später:
Ich würde einfach sagen, dass Portierung unmöglich ist. Es ist meistens in C, aber die meisten Leute würden nicht nennen, was ich schreibe C. Es verwendet alle erdenklichen 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 / aufgabenfeste Cookies benötigt).
Ich denke, meine eigenen Experimente mit x86 haben mich dazu veranlasst, diese Frage zu stellen. Linus hatte kein StackOverflow, also hat er es einfach implementiert, um es auszuprobieren.
quelle
Antworten:
Mit der Segmentierung wäre es beispielsweise möglich, jedes dynamisch zugewiesene Objekt (malloc) in ein eigenes Speichersegment zu legen. 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 differenziertere Zugriffskontrolle (mit Paging haben Sie nur 2 Schutzstufen: Benutzer und Supervisor) und robustere Betriebssystemkerne zu entwickeln. Beispielsweise hat nur Ring 0 vollen Zugriff auf die Hardware. Wenn Sie den Kern-Betriebssystemkern und die Gerätetreiber in die Ringe 0 und 1 unterteilen, können Sie ein robusteres und sehr schnelles Mikrokern-Betriebssystem erstellen, bei dem die meisten relevanten Zugriffskontrollen 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; Ein erneutes Laden ist ziemlich teuer und es ist möglich, gleichzeitig auf nur 8192 Segmente zuzugreifen. (Angenommen, Sie möchten die Anzahl der zugreifbaren Objekte maximieren, sodass das GDT nur Systemdeskriptoren und LDT-Deskriptoren enthält.)
Nun wird die Segmentierung im 64-Bit-Modus als "Legacy" bezeichnet, und Hardware-Limit-Prüfungen werden nur unter bestimmten Umständen durchgeführt. IMHO, ein großer Fehler. Eigentlich gebe ich Intel nicht die Schuld, sondern hauptsächlich den Entwicklern, die die Segmentierung für "zu kompliziert" hielten und sich nach einem flachen Adressraum sehnten. 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.)
quelle
Die kurze Antwort lautet, dass die Segmentierung ein Hack ist, der verwendet wird, um einen Prozessor mit einer eingeschränkten Fähigkeit zum Adressieren des Speichers zu veranlassen, diese Grenzen zu überschreiten.
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 16-Bit-Adressierung, wahrscheinlich aufgrund des Wunsches, die Konsistenz mit dem 8080 beizubehalten. Der Befehlssatz enthielt also 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 mit einer echten MMU, um 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 Beschrä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 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 eine interne 32-Bit-Architektur und einen physischen 24-Bit-Adressraum hatte.
quelle
Für 80x86 gibt es 4 Optionen - "nichts", nur Segmentierung, nur Paging und sowohl Segmentierung als auch Paging.
Für "nichts" (keine Segmentierung oder Paging) haben Sie 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, eine Position zu vermeiden Unabhängiger Code usw. Trotz all dieser Probleme kann es (theoretisch) in bestimmten 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 beinahe das Problem, einen Prozess vor sich selbst zu schützen, erfordert jedoch viele Umgehungen, um ihn nutzbar zu machen, wenn ein Prozess mehr als 8192 Segmente verwenden möchte (vorausgesetzt, ein LDT pro Prozess), was ihn zum größten Teil kaputt macht. Sie lösen fast das Problem "Prozesse voreinander schützen". Verschiedene Software-Komponenten, die auf derselben Berechtigungsstufe ausgeführt werden, können die Segmente des anderen laden / verwenden (es gibt Möglichkeiten, dies zu umgehen - GDT-Einträge während der Übertragung von Steuerelementen ändern und / oder LDTs verwenden). Es löst auch meistens das Problem des "positionsunabhängigen Codes" (es kann ein "segmentabhängiger Code" -Problem verursachen, aber das ist viel weniger bedeutend). Für das Problem der Fragmentierung des physischen Adressraums wird nichts unternommen.
Nur zum Blättern; es löst das Problem "einen Prozess vor sich selbst schützen" nicht sehr (aber seien wir ehrlich, dies ist nur ein Problem für das 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 "Fragmentierung des physischen Adressraums" vollständig. Als zusätzlichen Bonus eröffnen sich einige sehr leistungsfähige Techniken, die ohne Paging nicht annähernd so praktisch sind. Dazu gehören Dinge wie "Kopieren beim Schreiben", 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 ist dies möglich, mit der Ausnahme, dass der einzige Vorteil, den Sie durch die Segmentierung erzielen (was durch Paging nicht besser gemacht wird), eine Lösung für das Problem "Schützen 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 wenig Nutzen.
Aus diesem Grund verwenden fast alle Betriebssysteme, die für 80x86 entwickelt wurden, keine Segmentierung für die Speicherverwaltung (sie verwenden sie zum Beispiel für die Speicherung pro CPU und pro Task, aber dies dient hauptsächlich der Bequemlichkeit, um nicht ein nützlicheres Allzweckregister für diese zu verbrauchen Dinge).
Natürlich sind CPU-Hersteller nicht albern - sie werden nicht Zeit und Geld investieren, um etwas zu optimieren, von dem sie wissen, dass es niemand verwendet (sie werden etwas optimieren, das fast jeder verwendet). Aus diesem Grund optimieren CPU-Hersteller die Segmentierung nicht, was die Segmentierung langsamer macht, als dies der Fall sein könnte, weshalb OS-Entwickler dies noch mehr vermeiden möchten. Meistens wurde die Segmentierung nur aus Gründen der Abwärtskompatibilität beibehalten (was wichtig ist).
Schließlich hat AMD den Langzeitmodus entwickelt. Es gab keinen alten / existierenden 64-Bit-Code, um den man sich Sorgen machen müsste. Daher hat AMD (für 64-Bit-Code) so viel Segmentierung wie möglich beseitigt. Dies gab den OS-Entwicklern einen weiteren Grund (keine einfache Möglichkeit, für die Segmentierung auf 64-Bit konzipierten Code zu portieren), die Segmentierung weiterhin zu vermeiden.
quelle
Ich bin ziemlich verblüfft darüber, dass in all den Jahren, seit diese Frage gestellt wurde, 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 segmentierter virtueller Speichersysteme (zusammen mit symmetrischen Multiverarbeitungs- und hierarchischen Dateisystemen) erfunden oder in eine nützliche Form gebracht hat, war Multics (und siehe auch die Multicians- Site). Segmentierter Speicher ermöglicht es Multics, dem Benutzer anzuzeigen, dass sich alles im (virtuellen) Speicher befindet, und ermöglicht die ultimative Ebene der gemeinsamen Nutzung von allemin direkter Form (dh direkt im Speicher adressierbar). Das Dateisystem wird einfach zu einer Zuordnung zu allen Segmenten im Speicher. Wenn der segmentierte Speicher auf systematische Weise (wie bei Multics) ordnungsgemäß verwendet wird, ist der Benutzer nicht mehr mit der Verwaltung des Sekundärspeichers, der gemeinsamen Nutzung von Daten und der Kommunikation zwischen Prozessen belastet. Andere Antworten haben einige handgewellte Behauptungen aufgestellt, segmentierter Speicher sei schwieriger zu verwenden, aber dies ist einfach nicht wahr, und Multics hat dies vor Jahrzehnten mit durchschlagendem 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 eingesetzt 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 es scheint, haben allzu viele Menschen gelernt, die Lektionen der Vergangenheit zu ignorieren.
Intel hat auch früh versucht, ein leistungsfähigeres Supermikro namens iAPX 432 zu bauen , das zu diesem Zeitpunkt alles andere bei weitem übertroffen hätte, und es hatte eine segmentierte Speicherarchitektur und andere Funktionen, die stark auf objektorientierte Programmierung ausgerichtet waren. Die ursprüngliche Implementierung war jedoch zu langsam, und es wurden keine weiteren Versuche unternommen, sie zu beheben.
Eine detailliertere Diskussion darüber, wie Multics Segmentierung und Paging einsetzt, findet sich in Paul Green's Beitrag Multics Virtual Memory - Tutorial and Reflections
quelle
Die Segmentierung war ein Hack / Workaround, um die Adressierung von bis zu 1 MB Speicher durch einen 16-Bit-Prozessor zu ermöglichen - normalerweise wären nur 64 KB Speicher verfügbar gewesen.
Als 32 - Bit - Prozessoren hinzukamen, konnten Sie mit einem flachen Speichermodell bis zu 4 GB Arbeitsspeicher adressieren, und es war keine Segmentierung mehr erforderlich haben geschützten Modus 16-Bit).
Auch ein flacher Speichermodus ist für Compiler weitaus praktischer - Sie können segmentierte 16-Bit-Programme in C schreiben , aber es ist ein bisschen umständlich. Ein flaches Speichermodell macht alles einfacher.
quelle
Einige Architekturen (wie ARM) unterstützen überhaupt keine Speichersegmente. Wenn Linux von Segmenten abhängig gewesen wäre, hätte es nicht einfach auf diese Architekturen portiert werden können.
Betrachtet man das Gesamtbild, so hat das Versagen von Speichersegmenten mit der anhaltenden Beliebtheit von C- und Zeigerarithmetik zu tun. C-Entwicklung ist auf einer Architektur mit flachem Speicher praktischer. und wenn Sie einen flachen Speicher möchten, wählen Sie Speicher-Paging.
Es gab eine Zeit um die Wende der 80er Jahre, als Intel als Organisation die zukünftige Popularität von Ada und anderen höheren Programmiersprachen erwartete. Dies ist im Grunde, wo einige ihrer spektakuläreren Ausfälle, wie die schreckliche APX432 und 286 Speichersegmentierung, herkamen. Mit dem 386 kapitulierten sie vor Flachspeicherprogrammierern; Paging und ein TLB wurden hinzugefügt und die Segmente wurden auf 4 GB skalierbar gemacht. Und dann entfernte AMD im Grunde genommen Segmente mit x86_64, indem die Basisregistrierung zu einer Dont-Care / Implied-0 gemacht wurde (außer für TLS, denke ich).
Allerdings liegen die Vorteile von Speichersegmenten auf der Hand - das Umschalten von Adressräumen, ohne dass ein TLB neu aufgefüllt werden muss. Vielleicht wird irgendwann jemand eine leistungsfähige CPU bauen, 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 keine Flat benötigt -Speicherprogramme dafür.
quelle
Die Segmentierung ist eine enorme Belastung für Anwendungsentwickler. Hier kam der große Anstoß, die Segmentierung aufzuheben.
Interessanterweise frage ich mich oft, wie viel besser i86 sein könnte, wenn Intel die bisherige Unterstützung für diese alten Modi streichen würde. Besser wäre hier eine geringere Leistung und möglicherweise ein schnellerer Betrieb.
Ich denke, man könnte argumentieren, dass Intel die Milch mit 16-Bit-Segmenten sauer gemacht hat, was zu einer Art Entwickleraufstand führte. Aber seien wir ehrlich, ein 64-KB-Adressraum ist nichts Besonderes, wenn Sie sich eine moderne App ansehen. Am Ende mussten sie etwas unternehmen, weil die Konkurrenz die Adressraumprobleme von i86 effektiv bewältigen konnte und tat.
quelle
Die Segmentierung führt zu langsameren Seitenübersetzungen und Auslagerungen
Aus diesen Gründen wurde die Segmentierung auf x86-64 weitgehend eingestellt.
Der Hauptunterschied zwischen ihnen ist, dass:
Während es klüger erscheint, konfigurierbare Segmentbreiten zu haben, wenn Sie die Speichergröße für einen Prozess erhöhen, ist eine Fragmentierung unvermeidlich, z. B .:
wird irgendwann, wenn Prozess 1 wächst:
bis ein split unvermeidlich ist:
An diesem Punkt:
Bei Seiten mit fester Größe jedoch:
Speicherblöcke mit fester Größe sind einfach übersichtlicher und haben das aktuelle Betriebssystemdesign dominiert.
Siehe auch: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
quelle