web-dev-qa-db-fra.com

Pourquoi (pas) la segmentation?

J'étudie les systèmes d'exploitation et l'architecture x86, et pendant que je lisais sur la segmentation et la pagination, j'étais naturellement curieux de savoir 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 en faveur de la pagination. Quelques-unes des raisons pour lesquelles j'ai trouvé étaient la simplicité et la portabilité.

Quelles sont les utilisations pratiques de la segmentation (x86 ou autre) et verrons-nous jamais des systèmes d'exploitation robustes l'utiliser ou continueront-ils à favoriser un système basé sur la pagination.

Maintenant, je sais que c'est une question délicate, mais je suis curieux de savoir comment la segmentation serait gérée avec les nouveaux systèmes d'exploitation. Est-il tellement logique de favoriser la pagination que personne n'envisagera une approche plus "segmentée"? Si oui, pourquoi?


Et quand je dis segmentation "shun", j'implique que Linux ne l'utilise que dans la mesure du nécessaire. Seulement 4 segments pour les segments de code/données utilisateur et noyau. En lisant la documentation d'Intel, j'ai eu l'impression que la segmentation était conçue avec des solutions plus robustes à l'esprit. Là encore, on m'a dit à plusieurs reprises à quel point le x86 pouvait être compliqué.


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

Je dirais simplement que le portage est impossible. C'est surtout 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 me renseigner sur le 386. Comme déjà mentionné, il utilise une MMU , pour la pagination (pas encore sur le disque) et la segmentation. C'est la segmentation qui la rend VRAIMENT dépendante de 386 (chaque tâche a un segment de 64 Mo pour le code et les données - max 64 tâches en 4 Go. Quiconque a besoin de plus de 64 Mo/tâche - cookies difficiles).

Je suppose que ma propre expérimentation avec x86 m'a amené à poser cette question. Linus n'avait pas StackOverflow, donc il l'a juste implémenté pour l'essayer.

44
Mr. Shickadance

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 des segments et toute la classe des bogues de sécurité (dépassements de tampon) serait éliminée.

De plus, comme tous les décalages de segments commencent à zéro, tout le code compilé serait automatiquement indépendant de la position. Appeler dans un autre DLL se résumerait à un appel lointain à décalage constant (selon la fonction appelée). Cela simplifierait grandement les éditeurs de liens et les chargeurs.

Avec 4 anneaux de protection, il est possible de concevoir un contrôle d'accès plus fin (avec la pagination, vous n'avez que 2 niveaux de protection: utilisateur et superviseur) et des noyaux OS 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 principal et les pilotes de périphérique en anneaux 0 et 1, vous pourriez créer un système d'exploitation microkernel plus robuste et très rapide où la plupart des vérifications d'accès pertinentes seraient effectuées par HW. (Les pilotes de périphériques pouvaient accéder au matériel via le bitmap d'accès aux E/S dans le TSS.)

Cependant .. x86 est un peu limité. Il ne dispose que de 4 registres de segments de données "libres"; leur rechargement est assez cher, et il n'est possible d'accéder simultanément qu'à 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, avec le 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. À mon humble avis, une grosse erreur. En fait, je ne blâme pas Intel, je blâme surtout les développeurs, dont la majorité pensait que la segmentation était "trop ​​compliquée" et aspirait à un espace d'adressage plat. Je blâme également les auteurs d'OS qui manquaient d'imagination pour mettre à profit la segmentation. (AFAIK, OS/2 était le seul système d'exploitation à utiliser pleinement les fonctionnalités de segmentation.)

32
zvrba

La réponse courte est que la segmentation est un hack, utilisé pour faire en sorte qu'un processeur avec une capacité limitée à adresser 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'il pouvait physiquement accéder à 1 Mo de mémoire. Cependant, l'architecture interne était basée sur l'adressage 16 bits, probablement en raison du désir de conserver la cohérence avec le 8080. Le jeu d'instructions comprenait donc des registres de segments qui seraient combinés avec les index 16 bits pour permettre l'adressage de 1 Mo de mémoire complet . Le 80286 a étendu ce modèle avec une véritable MMU, pour prendre en charge la protection basée sur les segments et l'adressage de plus de mémoire (iirc, 16 Mo).

Dans le cas du PDP-11, les modèles ultérieurs du processeur ont fourni une segmentation en espaces d'instructions et de données, là encore pour prendre en charge les limitations d'un espace d'adressage 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 de 64 Ko. si vous aviez besoin d'accéder à plus que cela, vous devrez changer vos registres de segments. Ce qui signifiait, pour un programmeur C, que vous deviez dire au compilateur C quel type de pointeurs il devait générer.

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

26
parsifal

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

Pour "rien" (pas de segmentation ou de pagination), vous vous retrouvez sans aucun moyen facile de protéger un processus de lui-même, aucun moyen facile de protéger les processus les uns des autres, aucun moyen de gérer des choses comme la fragmentation de l'espace d'adressage physique, 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 intégré qui n'exécute qu'une seule application; ou peut-être quelque chose qui utilise JIT et virtualise tout de toute façon).

Pour la segmentation uniquement; il résout presque le problème de "protéger un processus contre lui-même", mais il faut beaucoup de solutions pour le rendre utilisable lorsqu'un processus veut utiliser plus de 8192 segments (en supposant un LDT par processus), ce qui le rend principalement cassé. Vous résolvez presque le problème de "protéger les processus les uns des autres"; mais différents logiciels exécutés au même niveau de privilège peuvent se charger/utiliser les segments les uns des autres (il existe des moyens de contourner cela - en modifiant les entrées GDT pendant les transferts de contrôle et/ou en utilisant les LDT). Il résout également le problème du "code indépendant de la position" (il peut provoquer un problème de "code dépendant du segment" mais c'est beaucoup moins important). Il 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 contre lui-même" (mais soyons honnêtes ici, ce n'est vraiment qu'un problème pour le débogage/test de code écrit dans des langages dangereux, et il existe de toute façon des outils beaucoup plus puissants comme valgrind). Il résout complètement le problème "protéger les processus les uns des autres", résout complètement le problème "position code indépendant" et résout complètement le problème "fragmentation de l'espace d'adressage physique". En prime, il ouvre des techniques très puissantes qui ne sont pas aussi pratiques sans pagination; y compris des choses comme "copier sur écriture", les fichiers mappés en mémoire, la gestion efficace de l'espace d'échange, etc.

Vous pensez maintenant que l'utilisation de la segmentation et de la pagination vous offrirait les avantages des deux; et en théorie, c'est possible, sauf que le seul avantage que vous obtenez de la segmentation (qui ne se fait pas mieux en paginant) est une solution au problème de "protéger un processus contre lui-même" dont personne ne se soucie vraiment. En pratique, ce que vous obtenez est la complexité des deux et les frais généraux des deux, pour très peu d'avantages.

C'est pourquoi presque tous les systèmes d'exploitation conçus pour 80x86 n'utilisent pas la segmentation pour la gestion de la mémoire (ils l'utilisent pour des choses comme le stockage par processeur et par tâche, mais c'est surtout pour des raisons de commodité afin d'éviter de consommer un registre à usage général plus utile pour ces derniers). des choses).

Bien sûr, les fabricants de CPU ne sont pas stupides - ils ne dépenseront pas de temps et d'argent à optimiser quelque chose qu'ils ne connaissent pas (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 n'ont conservé la segmentation que pour des raisons de compatibilité descendante (ce qui est important).

Finalement, AMD a conçu le mode long. Il n'y avait pas de code 64 bits ancien/existant à s'inquiéter, donc (pour le code 64 bits) AMD s'est débarrassé du plus de segmentation possible. Cela a donné aux développeurs de systèmes d'exploitation une autre raison (pas de moyen facile de porter le code conçu pour la segmentation sur 64 bits) de continuer à éviter la segmentation.

16
Brendan

Je suis plutôt stupéfait de constater que depuis que cette question a été publiée, personne n'a mentionné les origines des architectures de mémoire segmentée et la véritable puissance qu'elles peuvent se permettre.

Le système d'origine qui a inventé ou affiné sous forme utile toutes les fonctionnalités entourant la conception et l'utilisation de systèmes de mémoire virtuelle segmentée paginée (ainsi que les systèmes de fichiers multitraitement et hiérarchiques symétriques) était Multics (et voir aussi le site Multicians ). La mémoire segmentée permet à Multics d'offrir à l'utilisateur une vue que tout se trouve dans la mémoire (virtuelle), et il permet le niveau ultime de partage 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 charges liées à la gestion du stockage secondaire, au partage des données et aux communications inter-processus. D'autres réponses ont fait valoir que 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 des décennies.

Intel a créé une version entravée de la mémoire segmentée, la 80286, qui bien qu'elle soit assez puissante, ses limites l'empêchaient d'être utilisée pour quelque chose de vraiment utile. Le 80386 a amélioré ces limites, mais les forces du marché à l'époque ont à peu près empêché le succès de tout système qui pouvait vraiment tirer parti de ces améliorations. Depuis, il semble que trop de gens ont appris à ignorer les leçons du passé.

Intel a également essayé très tôt de construire un super-micro plus performant appelé iAPX 432 qui aurait dépassé de loin tout le reste à l'époque, et il avait une architecture de mémoire segmentée et d'autres fonctionnalités fortement orientées vers l'objet. programmation. L'implémentation d'origine était tout simplement trop lente cependant, et aucune autre tentative n'a été faite pour y remédier.

Une discussion plus détaillée sur la façon dont Multics a utilisé la segmentation et la pagination peut être trouvée dans l'article de Paul Green Multics Virtual Memory - Tutorial and Reflections

14
Greg A. Woods

La segmentation était un hack/solution de contournement permettant à jusqu'à 1 Mo de mémoire d'être adressée par un processeur 16 bits - normalement, seuls 64 Ko de mémoire auraient été accessibles.

Lorsque des processeurs 32 bits sont arrivés, vous pouviez adresser jusqu'à 4 Go de mémoire avec un modèle de mémoire plate et il n'y avait plus besoin de segmentation - Les registres de segment ont été redéfinis comme sélecteurs pour le GDT/pagination en mode protégé (bien que vous puissiez ont un mode protégé 16 bits).

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

6
Justin

Certaines architectures (comme ARM) ne prennent pas du tout en charge les segments de mémoire. Si Linux avait été dépendant des sources sur des segments, il n'aurait pas pu être porté très facilement sur ces architectures.

En regardant l'ensemble, l'échec des segments de mémoire a à voir avec la popularité continue du C et de l'arithmétique des pointeurs. Le développement en C est plus pratique sur une architecture à mémoire plate; et si vous voulez une mémoire plate, vous choisissez la pagination de la mémoire.

Il fut un temps au tournant des années 80 où Intel, en tant qu'organisation, anticipait la popularité future d'Ada et d'autres langages de programmation de niveau supérieur. C'est essentiellement de là que viennent certains de leurs échecs les plus spectaculaires, comme la terrible segmentation de la mémoire APX432 et 286. Avec le 386, ils ont capitulé devant les programmeurs de mémoire plate; la pagination 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 de la base reg un dont-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'adressage sans avoir à repeupler un TLB. Peut-être qu'un jour, quelqu'un créera un processeur compétitif en termes de performances qui prend en charge la segmentation, nous pouvons programmer un système d'exploitation axé sur la segmentation pour cela, et les programmeurs peuvent faire Ada/Pascal/D/Rust/un autre langage qui ne nécessite pas de mise à plat -programmes de mémoire pour cela.

5
Ted Middleton

La segmentation est un énorme fardeau pour les développeurs d'applications. C'est de là que vient le grand Push pour supprimer la segmentation.

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

Je suppose que l'on pourrait affirmer qu'Intel a aigri le lait avec des segments 16 bits, ce qui a provoqué une révolte des développeurs. Mais avouons-le, un espace d'adressage de 64 Ko n'est rien, surtout lorsque vous regardez une application moderne. En fin de compte, ils ont dû faire quelque chose parce que la concurrence pouvait et a effectivement marché contre les problèmes d'espace d'adressage d'i86.

1
Dave

La segmentation entraîne des traductions et des échanges de pages plus lents

Pour ces raisons, la segmentation a été largement 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 d'avoir des largeurs de segment configurables, lorsque vous augmentez la taille de la mémoire pour un processus, la fragmentation est inévitable, par exemple:

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

deviendra finalement à mesure que 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:

  • la seule façon de traduire des pages est de faire des recherches binaires sur toutes les pages du processus 1, ce qui prend un journal inacceptable (n)
  • un échange hors du processus 1 partie 1 pourrait être énorme car ce segment pourrait être énorme

Cependant, avec des pages de taille fixe:

  • chaque traduction 32 bits ne fait que 2 lectures en mémoire: marche dans le répertoire et la table des pages
  • chaque swap est un 4KiB acceptable

Les blocs de mémoire de taille fixe sont tout simplement plus faciles à gérer et ont dominé la conception actuelle du système d'exploitation.

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