web-dev-qa-db-fra.com

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

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?

enter image description here

From Comprendre le noyau Linux,

est-il correct que ce qui suit signifie que l'unité de segmentation dans MMU mappe les segments et 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).

enter image description here

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 en 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.

27
Tim

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 que lors de l'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 :-).

21
sourcejedi

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 segments 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.

9
ilkkachu

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à mis en évidence une partie, Linux utilise la segmentation de manière très limitée. La deuxième partie est 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.

8
RalfFriedl

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 limite = max en mode 32 bits, le même que le matériel impose qu'en 64 -bit mode , ce qui rend le type de segmentation résiduel. (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 étiez en utilisant un modèle de mémoire segmentée (en particulier sans mémoire virtuelle paginée), vous pourriez avoir des données et des adresses BSS relatives à DS base de segment, pile relative à la base SS et code relatif à l'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 (PAE hérité) ou 52-bit (x86-64) adresse physique. ( https://stackoverflow.com/questions/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 pas vous permet d'utiliser plus de 32 ou 64 bits d'espace d'adressage virtuel dans un seul processus (ou thread) , parce que 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 soit 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 faites avez une base de segment non nulle, les charges ont une latence supplémentaire car le cas "spécial" (normal) de contournement en ajoutant 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 par en vigueur 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 de bien meilleure façon par pagination au format PAE ou x86-64 page-table.)

  • TLS (Thread Local Storage): FS et GS sont pas fixés à la base = 0 en mode long. (Ils étaient nouveaux avec 386, et n'étaient utilisés implicitement par aucune instruction, pas même les instructions rep- string qui utilisent ES). x86-64 Linux définit l'adresse de base FS pour chaque thread à 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) vs utilisateur (anneau 3)), donc même lors du retour à l'espace utilisateur 64 bits, le noyau doit toujours faire en sorte que CS change, en utilisant iret ou sysret au lieu d'une instruction jump ou ret normale.

  • Dans x86-64, le point d'entrée syscall utilise swapgs pour 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 thread-local). L'instruction syscall ne change 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ée1.

  • 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 trucs 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 dans la conception de syscall, ce qui l'a rendu utilisable. Voir les liens dans cette réponse pour plus de détails.

3
Peter Cordes

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.

3
Dmitry Grigoryev