Quelle est la différence entre l’espace utilisateur et l’espace noyau?

73

L'espace du noyau est-il utilisé lorsque le noyau s'exécute au nom du programme utilisateur, à savoir l'appel système? Ou s'agit-il de l'espace d'adressage de tous les threads du noyau (par exemple, le planificateur)?

Si c'est le premier, cela signifie-t-il que le programme utilisateur normal ne peut pas avoir plus de 3 Go de mémoire (si la division est de 3 Go + 1 Go)? De plus, dans ce cas, comment le noyau peut-il utiliser la mémoire haute, car à quelle adresse de mémoire virtuelle les pages de la mémoire haute seront mappées, un espace de 1 Go d'espace du noyau étant logiquement mappé?

Poojan
la source

Réponses:

93

L'espace du noyau est-il utilisé lorsque le noyau s'exécute au nom du programme utilisateur, à savoir l'appel système? Ou s'agit-il de l'espace d'adressage de tous les threads du noyau (par exemple, le planificateur)?

Oui et oui.

Avant d'aller plus loin, nous devrions dire ceci à propos de la mémoire.

La mémoire se divise en deux zones distinctes:

  • L'espace utilisateur , qui est un ensemble d'emplacements où s'exécutent des processus utilisateur normaux (c'est-à-dire tout ce qui est différent du noyau). Le noyau a pour rôle de gérer les applications s'exécutant dans cet espace afin d'éviter tout problème avec la machine.
  • L'espace du noyau , qui est l'emplacement où le code du noyau est stocké et exécuté sous.

Les processus exécutés sous l'espace utilisateur n'ont accès qu'à une partie limitée de la mémoire, alors que le noyau a accès à toute la mémoire. Les processus exécutés dans l'espace utilisateur n'ont également pas accès à l'espace du noyau. Les processus de l'espace utilisateur ne peuvent accéder qu'à une petite partie du noyau via une interface exposée par le noyau - les appels système . Si un processus effectue un appel système, une interruption logicielle est envoyée au noyau, qui distribue ensuite le gestionnaire d'interruptions approprié et poursuit son travail une fois que le gestionnaire est terminé.

Le code d’espace du noyau a la propriété de s’exécuter en "mode noyau", ce qui (dans votre ordinateur de bureau typique) est ce que vous appelez un code qui s’exécute sous l’anneau 0 . Généralement, dans l'architecture x86, il existe 4 anneaux de protection . Ring 0 (mode noyau), Ring 1 (peut être utilisé par les hyperviseurs de machines virtuelles ou les pilotes), Ring 2 (peut être utilisé par les pilotes, je n'en suis cependant pas si sûr). La sonnerie 3 correspond aux applications courantes. C'est l'anneau le moins privilégié et les applications qui y sont exécutées ont accès à un sous-ensemble d'instructions du processeur. Ring 0 (espace noyau) est la sonnerie la plus privilégiée et a accès à toutes les instructions de la machine. Par exemple, une application "ordinaire" (comme un navigateur) ne peut pas utiliser les instructions d'assemblage x86.lgdtpour charger la table de descripteur global ou hltpour arrêter un processeur.

Si c'est le premier, cela signifie-t-il que le programme utilisateur normal ne peut pas avoir plus de 3 Go de mémoire (si la division est de 3 Go + 1 Go)? De plus, dans ce cas, comment le noyau peut-il utiliser la mémoire haute, car à quelle adresse de mémoire virtuelle les pages de la mémoire haute seront mappées, un espace de 1 Go d'espace du noyau étant logiquement mappé?

Pour une réponse à cette question, veuillez vous reporter à l'excellente réponse de wag ici

NlightNFotis
la source
4
N'hésitez pas à me dire si j'ai commis une erreur quelque part. Je suis nouveau dans la programmation noyau, et j’ai déposé ici ce que j’ai appris jusqu’à présent, ainsi que d’autres informations que j’ai trouvées sur le Web. Ce qui signifie que ma compréhension des concepts peut être déficiente dans le texte.
NlightNFotis
Merci! Je pense que maintenant je le comprends mieux. Juste pour m'assurer que je comprends bien, j'ai une autre question. Encore une fois, en considérant que les premiers 3 Go sont utilisés pour l’espace utilisateur et que 128 Mo d’espace noyau sont utilisés pour la mémoire vive, les 896 Mo restants (mémoire faible) sont-ils mappés statiquement au démarrage?
Poojan
1
@NlightNFotis Je dis que près de 15 personnes pensent que tout ce que vous avez dit est correct (c'est ce que vous nous faites penser;))
Braiam
Je pensais que la bague x86 -1était réservée aux hyperviseurs? fr.wikipedia.org/wiki/Protection_ring
Dori
1
Notez la différence entre la mémoire virtuelle et la mémoire physique. La plupart de vos questions concernent la mémoire virtuelle. Ceci est mappé sur la mémoire physique, cela devient compliqué à mesure que la mémoire physique approche les 3 Go et que PAE est utilisé. Cela redevient simple lorsqu'un noyau 64 bits est utilisé, dans ce cas, les adresses négatives sont réservées au noyau et les adresses positives à l'espace utilisateur. Les processus 32 bits peuvent maintenant utiliser 4 Go d'espace virtuel. Les processus 64 bits peuvent en utiliser beaucoup plus - généralement une valeur de 48 bits (pour le moment sur x86-64).
ctrl-alt-delor
16

Les anneaux de la CPU sont la distinction la plus claire

En mode protégé x86, la CPU est toujours dans l'une des 4 sonneries. Le noyau Linux utilise uniquement 0 et 3:

  • 0 pour le noyau
  • 3 pour les utilisateurs

C'est la définition la plus dure et la plus rapide du noyau par rapport au pays utilisateur.

Pourquoi Linux n'utilise pas les anneaux 1 et 2: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used

Comment l'anneau actuel est-il déterminé?

La sonnerie actuelle est sélectionnée par une combinaison de:

  • table de descripteur global: une table en mémoire d'entrées GDT, et chaque entrée a un champ Privlqui code l'anneau.

    L'instruction LGDT définit l'adresse sur la table de descripteur actuelle.

    Voir aussi: http://wiki.osdev.org/Global_Descriptor_Table

  • le segment enregistre CS, DS, etc., qui pointe vers l'index d'une entrée dans le GDT.

    Par exemple, CS = 0signifie que la première entrée du GDT est actuellement active pour le code en cours d’exécution.

Que peut faire chaque anneau?

La puce du processeur est construite physiquement de sorte que:

  • la bague 0 peut faire n'importe quoi

  • L'anneau 3 ne peut pas exécuter plusieurs instructions et écrire dans plusieurs registres, notamment:

    • ne peut pas changer sa propre bague! Sinon, il pourrait se mettre à sonner 0 et les sonneries seraient inutiles.

      En d'autres termes, vous ne pouvez pas modifier le descripteur de segment actuel , qui détermine la sonnerie actuelle.

    • ne peut pas modifier les tables de page: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work

      En d'autres termes, vous ne pouvez pas modifier le registre CR3 et la pagination elle-même empêche la modification des tables de pages.

      Cela empêche un processus de voir la mémoire d'autres processus pour des raisons de sécurité / facilité de programmation.

    • ne peut pas enregistrer les gestionnaires d'interruptions. Ceux-ci sont configurés en écrivant dans des emplacements de mémoire, ce qui est également empêché par la pagination.

      Les gestionnaires s'exécutent dans l'anneau 0 et briseraient le modèle de sécurité.

      En d'autres termes, vous ne pouvez pas utiliser les instructions LGDT et LIDT.

    • ne peut pas faire d'instructions IO comme inet out, et a donc des accès matériels arbitraires.

      Autrement, par exemple, les autorisations de fichiers seraient inutiles si un programme pouvait directement lire à partir du disque.

      Plus précisément, grâce à Michael Petch : il est en fait possible pour le système d’exploitation d’autoriser les instructions IO sur l’anneau 3, ce contrôle étant effectué par le segment d’état Tâche .

      Ce qui n’est pas possible, c’est que l’anneau 3 se donne la permission de le faire s’il ne l’avait pas eu en premier lieu.

      Linux le refuse toujours. Voir aussi: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss

Comment les programmes et les systèmes d'exploitation font-ils la transition entre les anneaux?

  • Lorsque la CPU est allumée, elle lance le programme initial dans l’anneau 0 (bien, mais c’est une bonne approximation). Vous pouvez penser que ce programme initial est le noyau (mais c'est normalement un chargeur de démarrage qui appelle ensuite le noyau toujours dans l'anneau 0).

  • lorsqu'un processus utilisateur veut que le noyau fasse quelque chose pour lui, comme écrire dans un fichier, il utilise une instruction qui génère une interruption, telle que int 0x80ousyscall pour signaler le noyau. x86-64 Linux exemple d'appels Bonjour tout le monde:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    compiler et exécuter:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHub en amont .

    Lorsque cela se produit, la CPU appelle un gestionnaire d’appel d’interruption que le noyau a enregistré au démarrage. Voici un exemple concret de baremetal qui enregistre un gestionnaire et l’utilise .

    Ce gestionnaire s'exécute dans l'anneau 0, qui décide si le noyau autorise cette action, effectue l'action et redémarre le programme utilisateur dans l'anneau 3. x86_64

  • lorsque l' execappel système est utilisé (ou lorsque le noyau démarre/init ), le noyau prépare les registres et la mémoire du nouveau processus utilisateur, puis il saute au point d'entrée et fait basculer la CPU vers la sonnerie 3

  • Si le programme essaie de faire quelque chose de méchant comme écrire dans un registre interdit ou une adresse mémoire (à cause de la pagination), la CPU appelle également un gestionnaire de rappel du noyau dans l'anneau 0.

    Mais comme le monde utilisateur était méchant, le noyau pourrait tuer le processus cette fois-ci ou lui donner un avertissement avec un signal.

  • Lorsque le noyau démarre, il configure une horloge matérielle avec une fréquence fixe, qui génère des interruptions périodiquement.

    Cette horloge matérielle génère des interruptions qui exécutent l'anneau 0 et lui permettent de planifier l'activation des processus utilisateur.

    De cette façon, la planification peut se produire même si les processus ne font aucun appel système.

Quel est l'intérêt d'avoir plusieurs anneaux?

La séparation des noyaux et des utilisateurs présente deux avantages majeurs:

  • il est plus facile de créer des programmes car vous êtes plus certain que l'un n'interférera pas avec l'autre. Par exemple, un processus utilisateur ne doit pas craindre d'écraser la mémoire d'un autre programme à cause de la pagination, ni de placer le matériel dans un état non valide pour un autre processus.
  • c'est plus sécurisé. Par exemple, les autorisations de fichiers et la séparation de la mémoire pourraient empêcher une application de piratage informatique de lire vos données bancaires. Cela suppose bien sûr que vous faites confiance au noyau.

Comment jouer avec?

J'ai créé une configuration de métal nu qui devrait être un bon moyen de manipuler directement les bagues: https://github.com/cirosantilli/x86-bare-metal-examples

Malheureusement, je n’ai pas eu la patience de donner un exemple d’utilisateur, mais j’ai été aussi loin que la configuration de la pagination, de sorte que l’utilisateur devrait être réalisable. J'aimerais voir une demande de traction.

Les modules du noyau Linux s'exécutent également dans l'anneau 0; vous pouvez donc les utiliser pour tester des opérations privilégiées, par exemple, lire les registres de contrôle: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-d'un-programme-obtenir-segmenta / 7419306 # 7419306

Voici une configuration pratique de QEMU + Buildroot pour l’essayer sans tuer votre hôte.

L'inconvénient des modules du noyau est que d'autres kthreads sont en cours d'exécution et peuvent interférer avec vos expériences. Mais en théorie, vous pouvez prendre en charge tous les gestionnaires d’interruptions avec votre module de noyau et posséder le système, ce serait un projet intéressant.

Anneaux négatifs

Bien que les anneaux négatifs ne soient pas réellement référencés dans le manuel Intel, il existe en réalité des modes de processeur qui ont des capacités supplémentaires par rapport à l’anneau 0 lui-même. Ils conviennent donc parfaitement au nom "anneau négatif".

Un exemple est le mode hyperviseur utilisé dans la virtualisation.

Pour plus de détails, voir: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1

BRAS

Dans ARM, les anneaux sont appelés niveaux d'exception, mais les idées principales restent les mêmes.

Il existe 4 niveaux d'exception dans ARMv8, couramment utilisés en tant que:

  • EL0: pays utilisateur

  • EL1: noyau ("superviseur" dans la terminologie ARM).

    Entré avec l' svcinstruction (SuperVisor Call), auparavant appelée swi assemblage antérieur , qui est l'instruction utilisée pour passer des appels système Linux. Bonjour exemple du monde ARMv8:

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHub en amont .

    Testez-le avec QEMU sur Ubuntu 16.04:

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    Voici un exemple concret de baremetal qui enregistre un gestionnaire SVC et effectue un appel SVC .

  • EL2: hyperviseurs , par exemple Xen .

    Entré avec l' hvcinstruction (appel HyperVisor).

    Un hyperviseur est un système d’exploitation, ce qu’est un système d’exploitation.

    Par exemple, Xen vous permet d'exécuter simultanément plusieurs systèmes d'exploitation, tels que Linux ou Windows, sur le même système. Il isole les systèmes d'exploitation les uns des autres pour des raisons de sécurité et de débogage, comme le fait Linux pour les programmes utilisateur.

    Les hyperviseurs sont un élément clé de l'infrastructure cloud actuelle: ils permettent à plusieurs serveurs de fonctionner sur un seul matériel, ce qui permet de maintenir l'utilisation du matériel à près de 100% et d'économiser beaucoup d'argent.

    AWS, par exemple, a utilisé Xen jusqu'en 2017, date à laquelle son passage à KVM a fait la une .

  • EL3: encore un autre niveau. TODO exemple.

    Entré avec l' smcinstruction (appel en mode sécurisé)

Le ARMv8 architecture Modèle de référence DDI 0487C.a - Chapitre D1 - Le modèle de système AArch64 Niveau programmeur - Figure D1-1 illustre cette magnifique:

entrez la description de l'image ici

Notez que ARM, peut-être en raison du recul, a une meilleure convention de dénomination pour les niveaux de privilège que x86, sans nécessiter de niveaux négatifs: 0 étant le plus bas et 3 le plus élevé. Les niveaux les plus élevés ont tendance à être créés plus souvent que les niveaux les plus bas.

Le EL actuel peut être interrogé avec l' MRSinstruction suivante: https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc

ARM n'exige pas que tous les niveaux d'exception soient présents pour permettre des implémentations qui n'ont pas besoin de la fonctionnalité pour enregistrer la zone de puce. ARMv8 "Niveaux d'exception" dit:

Une implémentation peut ne pas inclure tous les niveaux d'exception. Toutes les implémentations doivent inclure EL0 et EL1. EL2 et EL3 sont facultatifs.

Par exemple, QEMU est défini par défaut sur EL1, mais EL2 et EL3 peuvent être activés avec des options de ligne de commande: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up

Extraits de code testés sur Ubuntu 18.10.

Ciro Santilli 改造 中心 六四 事件
la source
3

Si c'est le premier, cela signifie-t-il que le programme utilisateur normal ne peut pas avoir plus de 3 Go de mémoire (si la division est de 3 Go + 1 Go)?

Oui, c'est le cas sur un système Linux normal. Un ensemble de correctifs "4G / 4G" flottant à un moment donné rendait les espaces d'adressage utilisateur et noyau complètement indépendants (à un coût de performances car il était plus difficile pour le noyau d'accéder à la mémoire de l'utilisateur), mais je ne pense pas. ils ont jamais été fusionnés en amont et l'intérêt a diminué avec la montée de x86-64

De plus, dans ce cas, comment le noyau peut-il utiliser la mémoire haute, car à quelle adresse de mémoire virtuelle les pages de la mémoire haute seront mappées, un espace de 1 Go d'espace du noyau étant logiquement mappé?

Auparavant, Linux fonctionnait (et le fait encore sur des systèmes où la mémoire est petite par rapport à l'espace d'adressage): toute la mémoire physique était mappée en permanence dans la partie noyau de l'espace d'adressage. Cela a permis au noyau d'accéder à toute la mémoire physique sans aucun remappage, mais il est évident qu'il ne s'adapte pas aux machines 32 bits disposant de beaucoup de mémoire physique.

Ainsi, le concept de mémoire haute et basse était né. La mémoire "basse" est mappée en permanence dans l'espace d'adressage du noyau. la mémoire "haute" n'est pas.

Lorsque le processeur exécute un appel système, il s'exécute en mode noyau, mais toujours dans le contexte du processus en cours. Ainsi, il peut accéder directement à la fois à l'espace adresse du noyau et à l'espace adresse utilisateur du processus en cours (en supposant que vous n'utilisiez pas les correctifs 4G / 4G susmentionnés). Cela signifie que la mémoire "haute" ne doit pas être affectée à un processus utilisateur.

L'utilisation de mémoire "haute" à des fins de noyau pose davantage de problèmes. Pour accéder à une mémoire haute qui n'est pas mappée sur le processus actuel, vous devez la mapper temporellement dans l'espace d'adressage du noyau. Cela signifie du code supplémentaire et une pénalité de performance.

plugwash
la source