GNU/Linux >> Tutoriels Linux >  >> Linux

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

L'espace du noyau est-il utilisé lorsque le noyau s'exécute au nom du programme utilisateur, c'est-à-dire l'appel système ? Ou est-ce l'espace d'adressage pour 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 est divisée en deux zones distinctes :

  • L'espace utilisateur , qui est un ensemble d'emplacements où s'exécutent les processus utilisateur normaux (c'est-à-dire tout autre que le noyau). Le rôle du noyau est de gérer les applications s'exécutant dans cet espace afin qu'elles ne se gênent pas entre elles et avec la machine.
  • L'espace noyau , qui est l'emplacement où le code du noyau est stocké et s'exécute sous.

Les processus s'exécutant 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 ne le font pas non plus avoir accès à l'espace 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 alors le gestionnaire d'interruption approprié et continue son travail une fois le gestionnaire terminé.

Le code d'espace du noyau a la propriété de s'exécuter en "mode noyau", qui (sur votre ordinateur de bureau typique -x86-) est ce que vous appelez le code qui s'exécute sous le ring 0 . Généralement dans l'architecture x86, il y a 4 anneaux de protection . Ring 0 (mode noyau), Ring 1 (peut être utilisé par les hyperviseurs ou les pilotes de machines virtuelles), Ring 2 (peut être utilisé par les pilotes, je n'en suis pas si sûr). L'anneau 3 est sous lequel les applications typiques s'exécutent. C'est l'anneau le moins privilégié et les applications qui y sont exécutées ont accès à un sous-ensemble des instructions du processeur. L'anneau 0 (espace noyau) est l'anneau le plus privilégié et a accès à toutes les instructions de la machine. Par exemple, une application "simple" (comme un navigateur) ne peut pas utiliser les instructions d'assemblage x86 lgdt pour charger la table des descripteurs globaux ou hlt pour 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-elles mappées, car 1 Go d'espace noyau sera logiquement mappé ?

Pour une réponse à cela, veuillez vous référer à l'excellente réponse par wag ici


Les anneaux de processeur sont la distinction la plus claire

En mode protégé x86, le processeur est toujours dans l'un des 4 anneaux. Le noyau Linux n'utilise que 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 à l'espace 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 la sonnerie actuelle est-elle déterminée ?

L'anneau actuel est sélectionné par une combinaison de :

  • table de description globale :une table en mémoire d'entrées GDT, et chaque entrée a un champ Privl qui encode l'anneau.

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

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

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

    Par exemple, CS = 0 signifie 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 CPU est physiquement construite de sorte que :

  • ring 0 peut tout faire

  • ring 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, ne peut pas modifier le descripteur de segment en cours, qui détermine la sonnerie en cours.

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

      En d'autres termes, ne peut 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 des autres processus pour des raisons de sécurité/facilité de programmation.

    • ne peut pas enregistrer les gestionnaires d'interruption. 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, ne peut pas utiliser les instructions LGDT et LIDT.

    • ne peut pas faire des instructions IO comme in et out , et ont donc des accès matériels arbitraires.

      Sinon, par exemple, les autorisations de fichiers seraient inutiles si n'importe quel programme pouvait lire directement à 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 d'E/S sur l'anneau 3, ceci est en fait contrôlé par le segment d'état de la 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 en premier lieu.

      Linux l'interdit 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 effectuent-ils la transition entre les anneaux ?

  • lorsque le processeur est allumé, il commence à exécuter le programme initial dans l'anneau 0 (enfin, mais c'est une bonne approximation). Vous pouvez considérer ce programme initial comme étant le noyau (mais c'est normalement un bootloader qui appelle ensuite le noyau toujours en ring 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 0x80 ou syscall pour signaler le noyau. x86-64 Linux syscall hello world example :

    .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, le processeur appelle un gestionnaire de rappel d'interruption que le noyau a enregistré au démarrage. Voici un exemple baremetal concret qui enregistre un gestionnaire et l'utilise.

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

  • quand le exec l'appel système est utilisé (ou lorsque le noyau démarrera /init ), le noyau prépare les registres et la mémoire du nouveau processus userland, puis il saute au point d'entrée et bascule le CPU sur l'anneau 3

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

    Mais puisque l'espace utilisateur était méchant, le noyau pourrait tuer le processus cette fois, 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 périodiquement des interruptions.

    Cette horloge matérielle génère des interruptions qui exécutent l'anneau 0 et lui permettent de programmer les processus utilisateur à réveiller.

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

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

Il y a deux avantages majeurs à séparer le noyau et l'espace utilisateur :

  • 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 n'a pas à se soucier d'écraser la mémoire d'un autre programme à cause de la pagination, ni de mettre le matériel dans un état invalide pour un autre processus.
  • c'est plus sûr. Par exemple. les autorisations de fichiers et la séparation de la mémoire pourraient empêcher une application de piratage de lire vos données bancaires. Cela suppose, bien sûr, que vous fassiez confiance au noyau.

Comment jouer avec ?

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

Je n'ai malheureusement pas eu la patience de faire un exemple d'espace utilisateur, mais je suis allé jusqu'à la configuration de la pagination, donc l'espace utilisateur devrait être faisable. J'aimerais voir une pull request.

Alternativement, les modules du noyau Linux s'exécutent dans l'anneau 0, vous pouvez donc les utiliser pour essayer des opérations privilégiées, par ex. lire les registres de contrôle :https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers-cr0-cr2-cr3-from-a-program-getting-segmenta/7419306#7419306

Voici une configuration QEMU + Buildroot pratique 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 pourraient 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 en fait un projet intéressant.

Sonneries négatives

Bien que les sonneries négatives ne soient pas réellement référencées dans le manuel d'Intel, il existe en fait des modes CPU qui ont plus de capacités que la sonnerie 0 elle-même, et conviennent donc bien au nom de "sonnerie négative".

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
  • https://security.stackexchange.com/questions/216527/ring-3-exploits-and-existence-of-other-rings

ARM

Dans ARM, les anneaux sont plutôt 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 comme :

  • EL0 :espace utilisateur

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

    Saisie avec le svc instruction (Appel SuperVisor), anciennement connue sous le nom de swi avant l'assemblage unifié, qui est l'instruction utilisée pour effectuer des appels système Linux. Hello world Exemple ARMv8 :

    bonjour.S

    .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 baremetal concret qui enregistre un gestionnaire SVC et effectue un appel SVC.

  • EL2 :hyperviseurs, par exemple Xen.

    Saisie avec le hvc instruction (appel HyperVisor).

    Un hyperviseur est à un système d'exploitation ce qu'un système d'exploitation est à l'espace utilisateur.

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

    Les hyperviseurs sont un élément clé de l'infrastructure cloud d'aujourd'hui :ils permettent à plusieurs serveurs de fonctionner sur un seul matériel, en maintenant l'utilisation du matériel toujours proche de 100 % et en économisant beaucoup d'argent.

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

  • EL3 :encore un autre niveau. Exemple de TODO.

    Saisie avec le smc instruction (appel en mode sécurisé)

Le modèle de référence d'architecture ARMv8 DDI 0487C.a - Chapitre D1 - Le modèle du programmeur de niveau système AArch64 - Figure D1-1 illustre cela magnifiquement :

La situation ARM a un peu changé avec l'avènement des extensions d'hôte de virtualisation ARMv8.1 (VHE). Cette extension permet au noyau de fonctionner efficacement en EL2 :

VHE a été créé parce que les solutions de virtualisation du noyau Linux telles que KVM ont gagné du terrain sur Xen (voir par exemple le passage d'AWS à KVM mentionné ci-dessus), car la plupart des clients n'ont besoin que de machines virtuelles Linux et, comme vous pouvez l'imaginer, être tout en un seul projet, KVM est plus simple et potentiellement plus efficace que Xen. Alors maintenant, le noyau Linux hôte agit comme l'hyperviseur dans ces cas.

Notez comment ARM, peut-être grâce au recul, a une meilleure convention de dénomination pour les niveaux de privilège que x86, sans avoir besoin de niveaux négatifs :0 étant le plus bas et 3 le plus élevé. Les niveaux supérieurs ont tendance à être créés plus souvent que les niveaux inférieurs.

L'EL actuel peut être interrogé avec le MRS instruction :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 les implémentations qui n'ont pas besoin de la fonctionnalité pour économiser la zone de la puce. ARMv8 "Niveaux d'exception" indique :

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.

QEMU, par exemple, utilise par défaut 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.


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. Il y avait un ensemble de correctifs "4G/4G" flottant à un moment donné qui rendaient les espaces d'adressage utilisateur et noyau complètement indépendants (au détriment des performances car cela rendait plus difficile l'accès du noyau à la mémoire utilisateur) mais je ne pense pas ils ont été fusionnés en amont et l'intérêt a diminué avec l'essor 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-elles mappées, car 1 Go d'espace noyau sera logiquement mappé ?

La façon dont Linux fonctionnait (et fonctionne toujours sur les systèmes où la mémoire est petite par rapport à l'espace d'adressage) était que l'ensemble de la mémoire physique était mappé 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 remappage, mais il est clair qu'il ne s'adapte pas aux machines 32 bits avec beaucoup de mémoire physique.

Ainsi est né le concept de mémoire basse et haute. La mémoire "basse" est mappée en permanence dans l'espace d'adressage du noyau. la mémoire "élevée" ne l'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 d'adressage du noyau et à l'espace d'adressage utilisateur du processus actuel (en supposant que vous n'utilisez pas les correctifs 4G/4G susmentionnés). Cela signifie qu'il n'y a aucun problème pour que la mémoire "élevée" soit allouée à un processus utilisateur.

L'utilisation de la mémoire "élevée" à des fins de noyau est plus problématique. Pour accéder à la mémoire haute qui n'est pas mappée au processus en cours, elle doit être temporairement mappée dans l'espace d'adressage du noyau. Cela signifie du code supplémentaire et une pénalité de performance.


Linux
  1. pile du noyau et pile de l'espace utilisateur

  2. Quelle est la différence entre fsck et e2fsck ?

  3. Quelle est la différence entre root et sudo ?

  4. Quelle est la différence entre ls et l ?

  5. Quelle est la différence entre le noyau préemptif non préemptif, préemptif et préemptif sélectif ?

Quelle est la différence entre InnoDB et MyISAM ?

Quelle est la différence entre les noyaux macOS et Linux

Quelle est la différence entre Linux et Unix ?

Différence entre l'utilisateur Sudo et l'utilisateur root ?

Qu'est-ce qu'un Hyperviseur ? Quelle est la différence entre les types 1 et 2 ?

Quelle est la différence entre curl et Wget ?