Linux n'utilise-t-il pas la segmentation mais seulement la pagination?

24

L'interface de programmation Linux montre la disposition d'un espace d'adressage virtuel d'un processus. Chaque région du diagramme est-elle un segment?

entrez la description de l'image ici

De Comprendre le noyau Linux ,

est-il exact que ce qui suit signifie que l'unité de segmentation dans MMU mappe les segments et les décalages au sein des segments dans l'adresse de mémoire virtuelle, et l'unité de pagination mappe ensuite l'adresse de mémoire virtuelle à l'adresse de mémoire physique?

L'unité de gestion de la mémoire (MMU) transforme une adresse logique en une adresse linéaire au moyen d'un circuit matériel appelé unité de segmentation; par la suite, un deuxième circuit matériel appelé unité de radiomessagerie transforme l'adresse linéaire en une adresse physique (voir la figure 2-1).

entrez la description de l'image ici

Alors pourquoi dit-on que Linux n'utilise pas la segmentation mais seulement la pagination?

La segmentation a été incluse dans les microprocesseurs 80x86 pour encourager les programmeurs à diviser leurs applications en entités logiquement liées, telles que des sous-programmes ou des zones de données globales et locales. Cependant, Linux utilise la segmentation de manière très limitée. En fait, la segmentation et la pagination sont quelque peu redondantes, car les deux peuvent être utilisées pour séparer les espaces d'adressage physiques des processus: la segmentation peut affecter un espace d'adressage linéaire différent à chaque processus, tandis que la pagination peut mapper le même espace d'adressage linéaire dans différents espaces d'adressage physiques . Linux préfère la pagination à la segmentation pour les raisons suivantes:

• La gestion de la mémoire est plus simple lorsque tous les processus utilisent les mêmes valeurs de registre de segment, c'est-à-dire lorsqu'ils partagent le même ensemble d'adresses linéaires.

• L'un des objectifs de conception de Linux est la portabilité vers un large éventail d'architectures; Les architectures RISC, en particulier, ont un support limité pour la segmentation.

La version 2.6 de Linux utilise la segmentation uniquement lorsqu'elle est requise par l'architecture 80x86.

Tim
la source
Pouvez-vous préciser les éditions s'il vous plaît. Il peut également être utile de spécifier des noms d'auteur. Je sais au moins que la première vient d'une personnalité éminente. Cependant, les deux noms de titres sont un peu génériques, il n'était pas clair pour moi au début que vous parliez de livres :-).
sourcejedi
2
Re "La segmentation a été incluse dans les microprocesseurs 80x86 ...": Ce n'est tout simplement pas vrai. C'est un héritage des processeurs 808x, qui avaient des pointeurs de données 16 bits et des segments de mémoire de 64 Ko. Les pointeurs de segment vous ont permis de changer de segment pour adresser plus de mémoire. Cette architecture a été transférée au 80x86 (avec une taille de pointeur augmentée à 33 bits). De nos jours dans le modèle x86_64, vous avez des pointeurs 64 bits qui peuvent (théoriquement - je pense que seulement 48 bits sont réellement utilisés) adresser 16 exaoctets, donc les segments ne sont pas nécessaires.
jamesqf
2
@jamesqf, eh bien, le mode protégé 32 bits dans le 386 prend en charge des segments qui sont assez différents des pointeurs à l'échelle de 16 octets qu'ils sont dans le 8086, donc ce n'est pas simplement hérité. Cela ne veut évidemment rien dire de leur utilité.
ilkkachu
@jamesqf 80186 avait le même modèle de mémoire que 8086, pas de "33 bits"
Jasen
Pas de réponse, donc juste un commentaire: les segments et les pages ne sont comparables que dans le contexte de l'échange (par exemple, l'échange de pages vs l'échange de segments) et dans ce contexte, l'échange de pages souffle simplement l'échange de segments hors de l'eau. Si vous permutez des segments à l'intérieur / à l'extérieur, vous devrez permuter le segment entier , qui peut être de 2 à 4 Go. Cela n'a jamais été une vraie chose à utiliser sur x86. Avec les pages, vous pouvez toujours travailler sur des unités 4KB. Lorsqu'il s'agit d'accéder à la mémoire, les segments et les pages sont liés par le biais de tableaux de pages et la comparaison serait des pommes avec des oranges.
vhu

Réponses:

20

L'architecture x86-64 n'utilise pas la segmentation en mode long (mode 64 bits).

Quatre des registres de segments: CS, SS, DS et ES sont forcés à 0 et la limite à 2 ^ 64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

Il n'est plus possible pour l'OS de limiter les plages des "adresses linéaires" disponibles. Par conséquent, il ne peut pas utiliser la segmentation pour la protection de la mémoire; il doit reposer entièrement sur la pagination.

Ne vous inquiétez pas des détails des processeurs x86 qui ne s'appliqueraient qu'en cas d'exécution dans les modes 32 bits hérités. Linux pour les modes 32 bits n'est pas autant utilisé. Elle peut même être considérée "dans un état de négligence bénigne pendant plusieurs années". Voir Prise en charge x86 32 bits dans Fedora [LWN.net, 2017].

(Il arrive que Linux 32 bits n'utilise pas non plus la segmentation. Mais vous n'avez pas besoin de me faire confiance à ce sujet, vous pouvez simplement l'ignorer :-).

sourcejedi
la source
C'est un peu exagéré. la base / limite est fixée à 0 / -1 en mode long pour les segments d'origine 8086 d'origine (CS / DS / ES / SS), mais FS et GS ont toujours une base de segment arbitraire. Et le descripteur de segment chargé dans CS détermine si la CPU s'exécute en mode 32 ou 64 bits. Et l'espace utilisateur sur x86-64 Linux utilise FS pour le stockage local des threads ( mov eax, [fs:rdi + 16]). Le noyau utilise GS (après swapgs) pour trouver la pile de noyau du processus actuel dans le syscallpoint d'entrée. Mais oui, la segmentation n'est pas utilisée dans le cadre du principal mécanisme de gestion de la mémoire / protection de la mémoire du système d'exploitation.
Peter Cordes
C'est essentiellement ce que la citation dans la question signifiait par "La version 2.6 de Linux utilise la segmentation uniquement lorsque requis par l'architecture 80x86." Mais votre 2ème paragraphe est fondamentalement faux. Linux utilise la segmentation essentiellement à l'identique dans les modes 32 et 64 bits. c'est-à-dire base = 0 / limite = 2 ^ 32 ou 2 ^ 64 pour les segments classiques (CS / DS / ES / SS) qui sont utilisés implicitement par des instructions normales. Il n'y a rien "supplémentaire" à craindre dans le code Linux 32 bits; la fonctionnalité HW existe mais n'est pas utilisée.
Peter Cordes
@PeterCordes vous interprétez fondamentalement la mauvaise réponse :-). Je l'ai donc édité pour essayer de rendre mon argument moins ambigu.
sourcejedi
Bonne amélioration, maintenant ce n'est pas trompeur. Je suis totalement d'accord avec votre véritable argument, à savoir que vous pouvez et devez ignorer totalement la segmentation x86, car elle n'est utilisée que pour les choses de gestion du système osdev et pour TLS. Si vous souhaitez éventuellement en savoir plus, c'est beaucoup plus facile à comprendre après avoir déjà compris x86 asm avec un modèle de mémoire plate.
Peter Cordes
8

Comme le x86 a des segments, il n'est pas possible de ne pas les utiliser. Mais les adresses de base cs(segment de code) et ds(segment de données) sont définies sur zéro, de sorte que la segmentation n'est pas vraiment utilisée. Une exception est les données locales de thread, l'un des registres de segments normalement inutilisés pointe vers les données locales de thread. Mais c'est principalement pour éviter de réserver l'un des registres à usage général pour cette tâche.

Cela ne dit pas que Linux n'utilise pas de segmentation sur le x86, car cela ne serait pas possible. Vous avez déjà souligné une partie, Linux utilise la segmentation de manière très limitée . La deuxième partie est que Linux utilise la segmentation uniquement lorsque requis par l'architecture 80x86

Vous avez déjà cité les raisons, la pagination est plus facile et plus portable.

RalfFriedl
la source
7

Chaque région du diagramme est-elle un segment?

Non.

Alors que le système de segmentation (en mode protégé 32 bits sur un x86) est conçu pour prendre en charge des segments de code, de données et de pile distincts, en pratique, tous les segments sont définis sur la même zone de mémoire. Autrement dit, ils commencent à 0 et se terminent à la fin de la mémoire (*) . Cela rend les adresses logiques et les adresses linéaires égales.

C'est ce qu'on appelle un modèle de mémoire "plat", et est un peu plus simple que le modèle où vous avez des segments distincts, puis des pointeurs en leur sein. En particulier, un modèle segmenté nécessite des pointeurs plus longs, car le sélecteur de segment doit être inclus en plus du pointeur de décalage. (Sélecteur de segment 16 bits + décalage 32 bits pour un total de pointeur de 48 bits; contre seulement un pointeur plat de 32 bits.)

Le mode long 64 bits ne prend pas vraiment en charge la segmentation autre que le modèle de mémoire plate.

Si vous deviez programmer en mode protégé 16 bits sur le 286, vous auriez plus besoin de segments, car l'espace d'adressage est de 24 bits mais les pointeurs ne sont que de 16 bits.

(* Notez que je ne me souviens pas comment Linux 32 bits gère la séparation noyau / espace utilisateur. La segmentation permettrait cela en définissant les limites des segments de l'espace utilisateur afin qu'ils n'incluent pas l'espace du noyau. La pagination le permet car elle fournit un niveau de protection par page.)

Alors pourquoi dit-on que Linux n'utilise pas la segmentation mais seulement la pagination?

Le x86 a toujours les segments et vous ne pouvez pas les désactiver. Ils sont juste utilisés le moins possible. En mode protégé 32 bits, les segments doivent être configurés pour le modèle plat, et même en mode 64 bits, ils existent toujours en quelque sorte.

ilkkachu
la source
Huh, je suppose qu'un noyau 32 bits pourrait peut-être atténuer Meltdown moins cher que de changer les tables de pages en définissant des limites de segment sur CS / DS / ES / SS qui empêchent l'espace utilisateur d'accéder au-dessus de 2G ou 3G. (Le vuln Meltdown est une solution de contournement pour le bit noyau / utilisateur dans les entrées de table de pages, permettant à l'espace utilisateur de lire à partir de pages mappées uniquement au noyau). Les pages VDSO peuvent être mappées en haut de la 4G, cependant: / wrfsbaseest illégal en mode protégé / compat, uniquement en mode long, donc sur un espace utilisateur de noyau 32 bits ne pouvait pas mettre FS base haut.
Peter Cordes
Sur un noyau 64 bits, l'espace utilisateur 32 bits peut potentiellement atteindre un segment de code 64 bits, vous ne pouvez donc pas dépendre des limites de segment pour la protection contre la fusion, seulement peut-être dans un noyau 32 bits pur. (Ce qui présente de gros inconvénients sur les machines avec beaucoup de RAM physique, par exemple, manque de mem pour les piles de threads.) Quoi qu'il en soit, oui Linux protège la mémoire du noyau avec la pagination, laissant base / limit = 0 / -1 dans l'espace utilisateur pour la normale segments (pas FS / GS qui sont utilisés pour le stockage local des threads).
Peter Cordes
Avant que le bit NX ne soit pris en charge dans les tables de pages matérielles (PAE), certains des premiers correctifs de sécurité utilisaient la segmentation pour créer des piles non exécutables pour le code de l'espace utilisateur. par exemple linux.com/news/exec-shield-new-linux-security-feature (la publication d'Ingo Molnar mentionne "l'excellent patch de pile non exécutable de Solar Designer" ".)
Peter Cordes
3

Linux x86 / 32 n'utilise pas la segmentation dans le sens où il initialise tous les segments à la même adresse linéaire et limite. L'architecture x86 nécessite que le programme ait des segments: le code ne peut être exécuté qu'à partir du segment de code, la pile ne peut être localisée que dans le segment de pile, les données ne peuvent être manipulées que dans l'un des segments de données. Linux contourne ce mécanisme en définissant tous les segments de la même manière (avec des exceptions que votre livre ne mentionne pas de toute façon), afin que la même adresse logique soit valide dans n'importe quel segment. Cela équivaut en fait à ne pas avoir de segments du tout.

Dmitry Grigoryev
la source
2

Chaque région du diagramme est-elle un segment?

Ce sont 2 utilisations presque totalement différentes du mot "segment"

  • Segmentation x86 / registres de segments: les systèmes d'exploitation x86 modernes utilisent un modèle de mémoire plate où tous les segments ont la même base = 0 et la limite = max en mode 32 bits, le même que le matériel l'impose en mode 64 bits , ce qui rend la segmentation un peu vestigiale . (Sauf pour FS ou GS, utilisé pour le stockage local des threads même en mode 64 bits.)
  • Sections / segments de l'éditeur de liens / programme. ( Quelle est la différence de section et de segment au format de fichier ELF )

Les usages ont une origine commune: si vous avez utilisé un modèle de mémoire segmentée ( en particulier sans mémoire paginée virtuel), vous pouvez avoir les données et les adresses du SRS être relatif à la base de segment DS, la pile par rapport à la base de SS et le code par rapport à la Adresse de base CS.

Ainsi, plusieurs programmes différents pourraient être chargés vers différentes adresses linéaires, ou même déplacés après le démarrage, sans modifier les décalages 16 ou 32 bits par rapport aux bases de segments.

Mais alors vous devez savoir à quel segment un pointeur est relatif, vous avez donc des "pointeurs éloignés" et ainsi de suite. (Les programmes x86 16 bits réels n'avaient souvent pas besoin d'accéder à leur code en tant que données, ils pouvaient donc utiliser un segment de code 64k quelque part, et peut-être un autre bloc 64k avec DS = SS, la pile passant de décalages élevés et les données à ou un minuscule modèle de code avec toutes les bases de segments égales).


Comment la segmentation x86 interagit avec la pagination

Le mappage d'adresses en mode 32/64 bits est:

  1. segment: offset (base de segment impliquée par le registre contenant l'offset, ou remplacée par un préfixe d'instruction)
  2. Adresse virtuelle linéaire 32 ou 64 bits = base + décalage. (Dans un modèle de mémoire plate comme Linux, les pointeurs / décalages = adresses linéaires aussi. Sauf lors de l'accès à TLS par rapport à FS ou GS.)
  3. les tables de pages (mises en cache par TLB) sont mappées linéairement à 32 (mode hérité), 36 (hérité PAE) ou 52-bit (x86-64) adresse physique. ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

    Cette étape est facultative: la pagination doit être activée lors du démarrage en définissant un bit dans un registre de contrôle. Sans pagination, les adresses linéaires sont des adresses physiques.

Notez que la segmentation ne vous permet pas d'utiliser plus de 32 ou 64 bits d'espace d'adressage virtuel dans un seul processus (ou thread) , car l'espace d'adressage plat (linéaire) dans lequel tout est mappé n'a que le même nombre de bits que les décalages eux-mêmes. (Ce n'était pas le cas pour le x86 16 bits, où la segmentation était en fait utile pour utiliser plus de 64 Ko de mémoire avec principalement des registres et des décalages 16 bits.)


Le CPU met en cache les descripteurs de segment chargés à partir du GDT (ou LDT), y compris la base du segment. Lorsque vous déréférencer un pointeur, selon le registre dans lequel il se trouve, il est par défaut DS ou SS comme segment. La valeur de registre (pointeur) est traitée comme un décalage par rapport à la base du segment.

Étant donné que la base de segment est normalement nulle, les processeurs font un cas spécial. Ou d'un autre point de vue, si vous n'avez une base de segment non-zéro, les charges ont une latence supplémentaire parce que le « spécial » (normal) cas de contournement d' ajouter l'adresse de base ne s'applique pas.


Comment Linux configure les registres de segments x86:

La base et la limite de CS / DS / ES / SS sont toutes 0 / -1 en mode 32 et 64 bits. C'est ce qu'on appelle un modèle de mémoire plate car tous les pointeurs pointent vers le même espace d'adressage.

(Les architectes du processeur AMD ont neutralisé la segmentation en appliquant un modèle de mémoire plate pour le mode 64 bits parce que les systèmes d'exploitation traditionnels ne l'utilisaient pas de toute façon, à l'exception de la protection sans exécutable qui était fournie d'une bien meilleure manière en paginant avec le PAE ou x86- Format de table de 64 pages.)

  • TLS (Thread Local Storage): FS et GS ne sont pas fixes à base = 0 en mode long. (Ils étaient nouveaux avec 386, et n'étaient utilisés implicitement par aucune instruction, pas même les repinstructions -string qui utilisent ES). x86-64 Linux définit l'adresse de base FS pour chaque thread sur l'adresse du bloc TLS.

    Par exemple, mov eax, [fs: 16]charge une valeur 32 bits de 16 octets dans le bloc TLS pour ce thread.

  • le descripteur de segment CS choisit le mode dans lequel se trouve la CPU (mode protégé 16/32/64 bits / mode long). Linux utilise une seule entrée GDT pour tous les processus de l'espace utilisateur 64 bits et une autre entrée GDT pour tous les processus de l'espace utilisateur 32 bits. (Pour que le CPU fonctionne correctement, DS / ES doit également être défini sur des entrées valides, tout comme SS). Il choisit également le niveau de privilège (noyau (anneau 0) par rapport à l'utilisateur (anneau 3)), donc même lorsqu'il revient à l'espace utilisateur 64 bits, le noyau doit toujours s'arranger pour que CS change, en utilisant iretou à la sysretplace d'un normal instruction jump ou ret.

  • Dans x86-64, le syscallpoint d'entrée utilise swapgspour basculer GS de GS de l'espace utilisateur vers le noyau, qu'il utilise pour trouver la pile de noyau pour ce thread. (Un cas spécialisé de stockage local par thread). L' syscallinstruction ne modifie pas le pointeur de pile pour pointer vers la pile du noyau; il pointe toujours vers la pile utilisateur lorsque le noyau atteint le point d'entrée 1 .

  • DS / ES / SS doivent également être définis sur des descripteurs de segment valides pour que le processeur fonctionne en mode protégé / mode long, même si la base / limite de ces descripteurs est ignorée en mode long.

Donc, fondamentalement, la segmentation x86 est utilisée pour TLS, et pour les tâches osdev x86 obligatoires que le matériel vous oblige à faire.


Note de bas de page 1: histoire amusante: il existe des archives de listes de diffusion de messages entre les développeurs du noyau et les architectes AMD datant de quelques années avant la sortie du silicium AMD64, ce qui a entraîné des modifications de la conception de syscallsorte qu'il était utilisable. Voir les liens dans cette réponse pour plus de détails.

Peter Cordes
la source