Pourquoi (pas) segmentation?

42

J'étudie les systèmes d'exploitation et l'architecture x86. Pendant que je lisais des informations sur la segmentation et la pagination, j'étais naturellement curieux de voir comment les systèmes d'exploitation modernes gèrent la gestion de la mémoire. D'après ce que j'ai trouvé, Linux et la plupart des autres systèmes d'exploitation évitent essentiellement la segmentation au profit de la pagination. Certaines des raisons pour lesquelles j'ai trouvé que je trouvais étaient la simplicité et la portabilité.

Quelles sont les utilisations pratiques de la segmentation (x86 ou autre) et verrons-nous l’utilisation de systèmes d’exploitation robustes ou continueront-ils à privilégier un système basé sur la pagination?

Maintenant, je sais que la question est complexe, mais je suis curieux de savoir comment la segmentation serait traitée avec les systèmes d’exploitation nouvellement développés. Est-il tellement logique de favoriser la pagination que personne ne songe à adopter une approche plus «segmentée»? Si oui, pourquoi?


Et lorsque je parle de «segmentation», je sous-entends que Linux ne l'utilise que dans la mesure nécessaire. Seulement 4 segments pour les segments utilisateur et de code / données du noyau. En lisant la documentation d'Intel, j'ai eu le sentiment que la segmentation avait été conçue avec des solutions plus robustes. Là encore, on m'a souvent dit à quel point le x86 peut être compliqué.


J'ai trouvé cette anecdote intéressante après avoir été liée à "l'annonce" originale de Torvald pour Linux. Il a dit ceci quelques messages plus tard:

Simplement, je dirais que le portage est impossible. C'est principalement en C, mais la plupart des gens n'appelleraient pas ce que j'écris C. Il utilise toutes les fonctionnalités imaginables du 386 que j'ai pu trouver, car c'était aussi un projet pour m'instruire sur le 386. Comme déjà mentionné, il utilise un MMU , pour la pagination (pas encore sur le disque) et la segmentation. C’est la segmentation qui le rend VRAIMENT 386 (chaque tâche a un segment de 64 Mo pour le code et les données - 64 tâches au maximum en 4 Go. Quiconque a besoin de plus de 64 Mo / tâche - cookies difficiles).

Je suppose que mes propres expériences avec x86 m'ont amené à poser cette question. Linus n'avait pas StackOverflow, il l'a donc simplement mis en place pour l'essayer.

M. Shickadance
la source
Quel livre as-tu lu?
1
Je lis un certain nombre de livres. J'ai commencé à me poser cette question en lisant le manuel Intel Systems Programming (vol. 3), mais j'ai lu quelques mots sur la gestion de la mémoire sous Linux dans "Comprendre le noyau Linux" et d'autres sources en ligne.
M. Shickadance
En particulier, je lisais la section sur les tables de descripteurs locaux et j'étais curieuse de savoir comment les systèmes d'exploitation les utilisaient.
M. Shickadance
1
OpenBSD combine la segmentation x86 et la pagination pour obtenir une simulation de bits NX (fonctionnalité de sécurité empêchant l'exécution de pages de données). Peut-être que PaX l'a utilisé aussi.
Je ne connais presque rien sur le sujet. Je viens de taper une question de recherche pour voir les réponses aux plaintes concernant tous les systèmes d'exploitation actuellement utilisés. En regardant les plaintes, la plupart des gens utilisent des ordinateurs et maintenant des tablettes pour quelques tâches spécifiques. Alors, pourquoi ne pas allouer plus d’utilisation de mémoire pour effectuer ces tâches plus rapidement, au lieu de donner à tous les pièges des périphériques qui y exécutent un accès.

Réponses:

31

Avec la segmentation, il serait par exemple possible de placer chaque objet alloué dynamiquement (malloc) dans son propre segment de mémoire. Le matériel vérifierait automatiquement les limites de segment, et toute la classe de bogues de sécurité (dépassements de mémoire tampon) serait éliminée.

De plus, comme tous les décalages de segment commencent à zéro, tout le code compilé sera automatiquement indépendant de la position. L'appel dans une autre DLL se résumerait à un appel éloigné avec un décalage constant (en fonction de la fonction appelée). Cela simplifierait grandement les lieurs et les chargeurs.

Avec 4 anneaux de protection, il est possible de concevoir un contrôle d'accès plus détaillé (avec la pagination, vous ne disposez que de 2 niveaux de protection: utilisateur et superviseur) et des noyaux de système d'exploitation plus robustes. Par exemple, seul l'anneau 0 a un accès complet au matériel. En séparant le noyau du système d'exploitation et les pilotes de périphérique en anneaux 0 et 1, vous pouvez créer un système d'exploitation microkernel plus robuste et plus rapide dans lequel la plupart des vérifications d'accès pertinentes seront effectuées par HW. (Les pilotes de périphérique peuvent accéder au matériel via le bitmap d’accès aux E / S dans le TSS.)

Cependant .. x86 est un peu limité. Il n'a que 4 registres de segments de données "libres"; leur rechargement est assez coûteux, et il est possible d'accéder simultanément à 8192 segments. (En supposant que vous souhaitiez maximiser le nombre d'objets accessibles, le GDT ne contient que des descripteurs système et des descripteurs LDT.)

Désormais, en mode 64 bits, la segmentation est décrite comme "héritée" et les vérifications des limites matérielles ne sont effectuées que dans des circonstances limitées. IMHO, une grosse erreur. En fait, je ne blâme pas Intel, je blâme surtout les développeurs, la majorité d'entre eux pensant que la segmentation était "trop ​​compliquée" et attendait depuis longtemps un espace d'adressage plat. Je blâme aussi les auteurs de systèmes d’exploitation qui manquaient d’imagination pour tirer parti de la segmentation. (Autant que je sache, OS / 2 était le seul système d'exploitation à utiliser pleinement les fonctions de segmentation.)

zvrba
la source
1
C'est pourquoi j'ai laissé cela ouvert. Il y a sûrement quelques points de vue différents sur la question ...
M. Shickadance Le
1
@zvrba: Quelle superbe explication !!! Merci pour ça. Maintenant, j'ai un doute: ne pensez-vous pas qu'INTEL aurait pu remporter le gros lot en rendant les segments non superposés et compatibles avec 4 Go avec l'aide de la pagination? Je veux dire, si j’ai bien compris, la "segmentation avec pagination" n’est capable d’adresser qu’un maximum de 4 Go d’espace adresse de mémoire virtuelle. Et ça c'est des cacahuètes !!! Imaginez pouvoir disposer de segments de code, pile, données de 4 Go chacun et ne se chevauchant pas ou se chevauchant selon vos besoins! Et cela aurait été un succès majeur à l'époque, sans avoir à faire appel à une architecture 64 bits complète comme aujourd'hui.
Fante
1
Explication fantastique de la raison pour laquelle la segmentation est bonne. C'est vraiment dommage qu'il soit tombé à l'eau. Voici une élaboration avec plus de détails pour ceux qui sont curieux d’apprendre plus.
GDP2
1
Pas étonnant que j'aimais OS / 2! Quelle triste perte d'une technologie vraiment précieuse grâce à l'ignorance et au marketing.
Ylluminate
Quiconque pense que la segmentation est une bonne idée ne doit pas être assez âgé pour se rappeler à quel point la segmentation est affreuse. C'est affreux. Pratiquement tout le code C jamais écrit attend un espace d'adressage plat. Il est pratique de pouvoir regarder un pointeur et de voir son adresse, sans avoir à creuser dans la base du segment, en supposant que ce soit même possible, ce qui n'est pas le cas dans la segmentation en mode protégé x86, à moins que le noyau ne vous le permette. en quelque sorte, probablement avec un appel système très coûteux. L'échange n'est pas possible avec les segments, à moins d'échanger des segments entiers. La pagination est loin, loin supérieure.
Doug65536
25

La réponse courte est que la segmentation est un hack, utilisé pour faire en sorte qu'un processeur ayant une capacité limitée d'adresser de la mémoire dépasse ces limites.

Dans le cas du 8086, il y avait 20 lignes d’adresse sur la puce, ce qui signifie qu’elle pouvait accéder physiquement à 1 Mo de mémoire. Cependant, l’architecture interne reposait sur un adressage sur 16 bits, probablement du fait de la volonté de conserver une cohérence avec le 8080. Le jeu d’instructions comprenait donc des registres de segments qui seraient combinés aux index sur 16 bits pour permettre l’adressage de 1 Mo de mémoire totale. . Le 80286 a étendu ce modèle avec une véritable MMU afin de prendre en charge la protection par segment et l'adressage de davantage de mémoire (iirc, 16 Mo).

Dans le cas du PDP-11, les modèles ultérieurs du processeur prévoyaient une segmentation en espaces d'instruction et de données, afin de prendre en charge les limitations d'un espace d'adressage de 16 bits.

Le problème de la segmentation est simple: votre programme doit explicitement contourner les limites de l’architecture. Dans le cas du 8086, cela signifiait que le plus grand bloc de mémoire contigu auquel vous pouviez accéder était 64k. si vous aviez besoin d'accéder à plus que cela, vous devriez changer vos registres de segments. Ce qui signifiait, pour un programmeur C, que vous deviez indiquer au compilateur C quel type de pointeurs il devait générer.

Il était beaucoup plus facile de programmer le MC68k, doté d'une architecture interne à 32 bits et d'un espace d'adressage physique de 24 bits.


la source
5
Ok, tout cela a du sens. Cependant, en lisant les documents Intel, on serait enclin à penser que des segments pourraient en fait être utilisés pour une meilleure protection au niveau matériel contre les bogues du programme. Plus précisément, section 3.2.3 du Guide de programmation système - le modèle multizone présente-t-il des avantages? Serait-il correct de dire que Linux utilise le modèle plat protégé? (section 3.2.2)
M. Shickadance
3
Cela fait longtemps que je n’ai pas prêté attention aux détails de l’architecture de la mémoire Intel, mais je ne pense pas que l’architecture segmentée offrirait une meilleure protection matérielle. La seule véritable protection qu'une MMU peut vous offrir consiste à séparer le code et les données, en prévenant les attaques par dépassement de la mémoire tampon. Et je crois que c'est contrôlable sans segments, via des attributs au niveau de la page. Vous pouvez théoriquement restreindre l'accès aux objets en créant un segment distinct pour chacun, mais je ne pense pas que ce soit raisonnable.
1
Merci, vous avez ramené tous les souvenirs refoulés du traitement des images sur la mémoire segmentée - cela va signifier davantage de traitement!
Martin Beckett
10
Vous avez complètement mal compris la segmentation. En 8086, cela aurait pu être un bidouillage; 80286 a introduit le mode protégé où il était crucial pour la protection; dans 80386, il a été encore étendu et les segments peuvent dépasser 64 Ko, toujours avec l'avantage des vérifications matérielles. (BTW, 80286 n'a pas eu d'MMU.)
zvrba
2
En 1985, lorsque le 386 a été introduit, un espace d’adresse de 4 Gio était considéré comme énorme. N'oubliez pas qu'un disque dur de 20 Mio était plutôt volumineux à l'époque et qu'il n'était toujours pas rare que des systèmes utilisent uniquement des lecteurs de disquettes. Le FDD 3.5 "a été introduit en 1983, avec une capacité formatée de 360 ​​Ko. (1.44 MB disponible en 1986". de 64 bits: physiquement accessible, mais si grand qu'il est pratiquement infini.
un CVn
15

Pour 80x86, il y a 4 options - "rien", segmentation uniquement, pagination uniquement, et segmentation et pagination.

Pour "rien" (pas de segmentation ni de pagination), vous ne disposez d'aucun moyen simple de protéger un processus de lui-même, d'aucun moyen simple de protéger les processus les uns des autres, d'aucun moyen de gérer des tâches telles que la fragmentation de l'espace d'adressage physique, d'aucun moyen d'éviter la position code indépendant, etc. Malgré tous ces problèmes, il pourrait (en théorie) être utile dans certaines situations (par exemple, un périphérique embarqué qui n'exécute qu'une seule application; ou peut-être quelque chose qui utilise JIT et qui tout virtualise de toute façon).

Pour la segmentation seulement; il résout presque le problème de "protéger un processus de lui-même", mais il faut beaucoup de solutions pour le rendre utilisable lorsqu'un processus souhaite utiliser plus de 8192 segments (en supposant un LDT par processus), ce qui le rend quasiment cassé. Vous résolvez presque le problème de "protéger les processus les uns des autres"; mais différents logiciels s'exécutant au même niveau de privilège peuvent charger / utiliser leurs segments respectifs (il existe des moyens de contourner ce problème - modifier les entrées GDT lors des transferts de contrôle et / ou à l'aide des LDT). Cela résout aussi principalement le problème du "code indépendant de la position" (cela peut causer un problème du "code dépendant du segment" mais c'est beaucoup moins important). Cela ne fait rien pour le problème de "fragmentation de l'espace d'adressage physique".

Pour la pagination seulement; cela ne résout pas beaucoup le problème de "protéger un processus de lui-même" (mais soyons honnêtes ici, il ne s'agit que d'un problème de débogage / test de code écrit dans des langages non sécurisés, et il existe de toute façon des outils beaucoup plus puissants comme valgrind). Il résout complètement le problème de "protection des processus les uns contre les autres", résout complètement le problème de "code indépendant de la position" et résout complètement le problème de "fragmentation de l'espace d'adressage physique". En prime, cela ouvre des techniques très puissantes qui sont loin d'être aussi pratiques sans pagination; y compris des éléments tels que «copie à l'écriture», fichiers mappés en mémoire, gestion efficace de l'espace de permutation, etc.

Maintenant, vous penseriez que l'utilisation de la segmentation et de la pagination vous apporterait les avantages des deux; et théoriquement, cela est possible, sauf que le seul avantage que vous tirez de la segmentation (que la pagination ne permet pas mieux de résoudre) est une solution au problème de la "protection d'un processus contre lui-même", dont personne ne se soucie vraiment. En pratique, ce que vous obtenez est la complexité des deux et leurs frais généraux, pour un bénéfice minime.

C’est pourquoi la quasi-totalité des systèmes d’exploitation conçus pour le format 80x86 n’utilisent pas la segmentation pour la gestion de la mémoire (ils l’utilisent pour des tâches telles que le stockage par processeur et par tâche) des choses).

Bien sûr, les fabricants de processeurs ne sont pas stupides - ils ne vont pas dépenser temps et argent pour optimiser quelque chose qu'ils savent que personne n'utilise (ils vont optimiser quelque chose que presque tout le monde utilise à la place). Pour cette raison, les fabricants de CPU n'optimisent pas la segmentation, ce qui rend la segmentation plus lente qu'elle ne pourrait l'être, ce qui incite les développeurs de systèmes d'exploitation à l'éviter encore plus. La plupart du temps, ils ne conservaient que la segmentation pour des raisons de compatibilité ascendante (ce qui est important).

Finalement, AMD a conçu le mode long. Il n'y avait pas de code 64 bits ancien / existant à craindre, donc (pour le code 64 bits), AMD s'est débarrassé de la segmentation autant que possible. Cela donnait aux développeurs de systèmes d’exploitation une autre raison (continuer à éviter le code de port conçu pour la segmentation au format 64 bits) pour continuer à éviter la segmentation.

Brendan
la source
13

Je suis plutôt étonné que, depuis que cette question a été posée, personne n’a mentionné les origines des architectures de mémoire segmentées et le véritable pouvoir qu’elles peuvent se permettre.

Le système original qui a inventé ou affiné de manière utile toutes les fonctions entourant la conception et l'utilisation de systèmes de mémoire virtuelle paginée segmentés (ainsi que de systèmes de fichiers multitraitement et hiérarchiques symétriques) était Multics (et également le site Multicians ). La mémoire segmentée permet à Multics d’indiquer à l’utilisateur que tout est dans la mémoire (virtuelle) et permet le partage ultime de tout.sous forme directe (c'est-à-dire directement adressable en mémoire). Le système de fichiers devient simplement une carte de tous les segments en mémoire. Lorsqu'elle est correctement utilisée de manière systématique (comme dans Multics), la mémoire segmentée libère l'utilisateur des nombreuses tâches liées à la gestion du stockage secondaire, au partage de données et aux communications inter-processus. D’autres réponses ont fait l’affirmation selon laquelle la mémoire segmentée est plus difficile à utiliser, mais ce n’est tout simplement pas vrai, et Multics l’a prouvé avec un succès retentissant il y a plusieurs décennies.

Intel a créé une version tronquée de la mémoire segmentée, la 80286, qui, bien qu’elle soit assez puissante, n’a pu être utilisée à des fins utiles. Le 80386 a amélioré ces limitations, mais les forces du marché à l'époque ont pratiquement empêché le succès de tout système pouvant réellement tirer parti de ces améliorations. Dans les années qui ont suivi, il semble que trop de gens aient appris à ignorer les leçons du passé.

Intel a également essayé très tôt de créer un super-micro plus puissant appelé iAPX 432, qui aurait surpassé de loin tout ce qu’il avait à l’époque. Il possédait une architecture de mémoire segmentée et d’autres fonctionnalités fortement orientées vers la programmation orientée objet. L'implémentation initiale était cependant trop lente et aucune autre tentative n'a été faite pour y remédier.

Vous trouverez une discussion plus détaillée sur la segmentation et la pagination utilisées par Multics dans l'article de Paul Green, Multics Virtual Memory - Tutorial and Reflections.

Greg A. Woods
la source
1
Excellente information et arguments superbes. Merci pour les liens, ils n'ont pas de prix !!!
Fante
1
Merci pour votre lien vers Multics et pour la réponse très informative! Clairement, la segmentation était supérieure à bien des égards à ce que nous faisons maintenant.
GDP2
1
Votre réponse est un vrai bijou à l'état brut. Merci beaucoup de partager ces informations que nous avons perdues. Cela me donne envie de voir un retour à la segmentation via le développement d’un système d’exploitation adéquat qui pourrait inciter à affiner le matériel. Vraiment, de nombreux problèmes pourraient être résolus avec cette approche! Il semble même que nous puissions obtenir de vrais langages POO à des niveaux de performance bien supérieurs et à l’usine avec la segmentation.
Ylluminate
6

La segmentation était une solution de contournement permettant de traiter jusqu'à 1 Mo de mémoire par un processeur 16 bits - normalement, seulement 64 Ko de mémoire auraient été accessibles.

Lorsque les processeurs 32 bits arrivaient, vous pouviez adresser jusqu'à 4 Go de mémoire avec un modèle de mémoire à plat et il n'y avait plus besoin de segmentation. avoir le mode protégé 16 bits).

De plus, un mode de mémoire à plat est beaucoup plus pratique pour les compilateurs - vous pouvez écrire des programmes segmentés de 16 bits en C , mais c'est un peu fastidieux. Un modèle de mémoire à plat simplifie tout.

Justin
la source
Y a-t-il beaucoup à dire sur la «protection» fournie par la segmentation lorsque nous pouvons simplement utiliser la pagination à la place?
M. Shickadance
1
@M. La segmentation Shickadance ne fournit aucune sorte de protection de la mémoire - Pour la protection de la mémoire, vous avez besoin du mode protégé dans lequel vous pouvez protéger la mémoire à l'aide de la fonction GDT ou de la pagination.
Justin
5

Certaines architectures (comme ARM) ne supportent pas du tout les segments de mémoire. Si Linux avait été dépendant de la source sur des segments, il n'aurait pas pu être facilement porté sur ces architectures.

Dans l’ensemble, l’échec des segments de mémoire est lié à la popularité croissante de l’arithmétique de C et de pointeur. Le développement en C est plus pratique sur une architecture à mémoire plate; et si vous voulez une mémoire à plat, vous choisissez la pagination en mémoire.

Il fut un temps, au tournant des années 80, quand Intel, en tant qu’organisation, anticipait la popularité future d’Ada et d’autres langages de programmation de niveau supérieur. C’est d’où proviennent certains de leurs échecs les plus spectaculaires, tels que la terrible segmentation de la mémoire APX432 et 286,. Avec les 386, ils ont capitulé devant des programmeurs à mémoire plate; paging et un TLB ont été ajoutés et les segments ont été rendus redimensionnables à 4 Go. Et puis, AMD a essentiellement supprimé les segments avec x86_64 en faisant en sorte que la base reg soit un type ne-care / implied-0 (sauf pour fs? Pour TLS, je pense?)

Cela dit, les avantages des segments de mémoire sont évidents: changer d’espace d’adresse sans devoir repeupler un TLB. Peut-être qu'un jour un processeur performant prenant en charge la segmentation sera créé, nous pourrons programmer un système d'exploitation orienté segmentation et les programmeurs pourront créer Ada / Pascal / D / Rust / un autre langage qui ne nécessite pas de plat. programmes de mémoire pour cela.

Ted Middleton
la source
1

La segmentation est un lourd fardeau pour les développeurs d'applications. C’est de là que vient la grande pression pour éliminer la segmentation.

Fait intéressant, je me demande souvent à quel point i86 pourrait être meilleur si Intel supprimait tout le support existant pour ces anciens modes. Ici, mieux impliquerait une puissance inférieure et peut-être un fonctionnement plus rapide.

J'imagine que l'on pourrait soutenir qu'Intel a acheté du lait avec des segments de 16 bits conduisant à une révolte des développeurs. Mais avouons-le, un espace d'adressage de 64 Ko n'est rien, surtout quand on regarde une application moderne. En fin de compte, ils ont dû faire quelque chose parce que la concurrence pouvait et avait effectivement marché contre les problèmes d'espace d'adressage de i86.

Dave
la source
1

La segmentation ralentit les conversions et les conversions de pages

Pour ces raisons, la segmentation a été en grande partie abandonnée sur x86-64.

La principale différence entre eux est que:

  • la pagination divise la mémoire en morceaux de taille fixe
  • la segmentation permet différentes largeurs pour chaque morceau

Bien qu'il puisse sembler plus intelligent de disposer de largeurs de segment configurables, à mesure que vous augmentez la taille de la mémoire d'un processus, la fragmentation est inévitable, par exemple:

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

deviendra éventuellement comme le processus 1 se développe:

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

jusqu'à ce qu'une scission soit inévitable:

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

À ce point:

  • le seul moyen de traduire des pages est d'effectuer des recherches binaires sur toutes les pages du processus 1, ce qui prend un journal inacceptable (n)
  • un échange hors processus 1 partie 1 pourrait être énorme car ce segment pourrait être énorme

Avec des pages de taille fixe cependant:

  • chaque traduction 32 bits ne lit que 2 mémoires: répertoire et table de page
  • chaque échange est un 4 Ko acceptable

Des morceaux de mémoire de taille fixe sont tout simplement plus faciles à gérer et ont dominé la conception actuelle des systèmes d'exploitation.

Voir aussi: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work

Ciro Santilli 改造 心心
la source