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.lgdt
pour charger la table de descripteur global 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 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
-1
était réservée aux hyperviseurs? fr.wikipedia.org/wiki/Protection_ringLes 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:
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
Privl
qui 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 = 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 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
in
etout
, 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 0x80
ousyscall
pour signaler le noyau. x86-64 Linux exemple d'appels Bonjour tout le monde:compiler et exécuter:
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'
exec
appel 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 3Si 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:
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'
svc
instruction (SuperVisor Call), auparavant appeléeswi
assemblage antérieur , qui est l'instruction utilisée pour passer des appels système Linux. Bonjour exemple du monde ARMv8:GitHub en amont .
Testez-le avec QEMU sur Ubuntu 16.04:
Voici un exemple concret de baremetal qui enregistre un gestionnaire SVC et effectue un appel SVC .
EL2: hyperviseurs , par exemple Xen .
Entré avec l'
hvc
instruction (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'
smc
instruction (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:
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'
MRS
instruction suivante: https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etcARM 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:
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.
la source
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
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.
la source