Tous les processeurs modernes ont la capacité d' interrompre l'instruction machine en cours d'exécution. Ils sauvegardent suffisamment d’état (généralement, mais pas toujours, sur la pile) pour permettre la reprise ultérieure de l’exécution, comme si rien ne s’était passé (l’instruction interrompue sera redémarrée à partir de zéro, en général). Ensuite, ils commencent à exécuter un gestionnaire d’interruptions , qui est juste plus de code machine, mais placé à un emplacement spécial afin que la CPU sache où elle se trouve à l’avance. Les gestionnaires d'interruption font toujours partie du noyau du système d'exploitation: le composant qui s'exécute avec le plus grand privilège et est responsable de la supervision de l'exécution de tous les autres composants. 1,2
Les interruptions peuvent être synchrones , c'est-à-dire qu'elles sont déclenchées par la CPU elle-même en réponse directe à l'action de l'instruction en cours d'exécution, ou asynchrones , ce qui signifie qu'elles se produisent à un moment imprévisible en raison d'un événement externe, tel que les données arrivant sur le réseau. Port. Certaines personnes réservent le terme "interruption" pour les interruptions asynchrones et appellent des interruptions synchrones "traps", "faults" ou "exceptions", mais ces mots ont tous une autre signification, alors je vais m'en tenir à "interruption synchrone".
Aujourd'hui, la plupart des systèmes d'exploitation modernes ont une notion de processus . Il s’agit essentiellement d’un mécanisme selon lequel l’ordinateur peut exécuter plus d’un programme à la fois, mais c’est aussi un aspect essentiel de la façon dont les systèmes d’exploitation configurent la protection de la mémoire , caractéristique de la plupart des logiciels (mais, hélas, toujours pas tous ) les processeurs modernes. Cela va avec la mémoire virtuelle, qui permet de modifier le mappage entre les adresses de mémoire et les emplacements réels dans la RAM. La protection de la mémoire permet au système d'exploitation de donner à chaque processus son propre bloc de RAM privé, auquel il est le seul à pouvoir accéder. Il permet également au système d’exploitation (agissant pour le compte de certains processus) de désigner des régions de RAM comme étant en lecture seule, exécutables, partagées par un groupe de processus coopérants, etc. Il y aura également un bloc de mémoire uniquement accessible par le serveur. noyau. 3
Tant que chaque processus n'accède à la mémoire que de la manière que le processeur est configuré pour autoriser, la protection de la mémoire est invisible. Lorsqu'un processus enfreint les règles, le processeur génère une interruption synchrone, demandant au noyau de résoudre le problème. Il arrive régulièrement que le processus n'enfreigne pas vraiment les règles, seul le noyau doit effectuer certains travaux avant que le processus puisse continuer. Par exemple, si une page de la mémoire d'un processus doit être "expulsée" dans le fichier d'échange afin de libérer de l'espace dans la RAM pour autre chose, le noyau indiquera que cette page est inaccessible. La prochaine fois que le processus essaiera de l'utiliser, la CPU générera une interruption de protection de la mémoire. le noyau récupérera la page de swap, la remettra à sa place, la marquera de nouveau comme accessible et reprendra son exécution.
Mais supposons que le processus ait réellement enfreint les règles. Il a essayé d'accéder à une page sur laquelle aucune RAM n'a été mappée, ou a essayé d'exécuter une page marquée comme ne contenant pas de code machine, ou autre. La famille de systèmes d'exploitation généralement connue sous le nom "Unix" utilise tous des signaux pour faire face à cette situation. 4 Les signaux ressemblent aux interruptions, mais ils sont générés par le noyau et mis en champs par des processus, plutôt que par le matériel et par le noyau. Les processus peuvent définir des gestionnaires de signauxdans leur propre code, et dire au noyau où ils se trouvent. Ces gestionnaires de signaux s’exécuteront ensuite, interrompant le flux de contrôle normal, si nécessaire. Les signaux ont tous un numéro et deux noms, l'un étant un acronyme cryptique et l'autre une phrase légèrement moins cryptique. Le signal généré lorsque le processus enfreint les règles de protection de la mémoire est le numéro 11 (par convention), ainsi que ses noms SIGSEGV
et "Défaut de segmentation". 5,6
Une différence importante entre les signaux et les interruptions est qu’il existe un comportement par défaut pour chaque signal. Si le système d'exploitation ne parvient pas à définir des gestionnaires pour toutes les interruptions, il s'agit d'un bogue dans le système d'exploitation. Tout l'ordinateur se bloque lorsque le processeur tente d'appeler un gestionnaire manquant. Cependant, les processus ne sont nullement tenus de définir des gestionnaires de signaux pour tous les signaux. Si le noyau génère un signal pour un processus et que celui-ci a conservé son comportement par défaut, le noyau s'exécutera comme il se doit, quelle que soit la valeur par défaut, sans déranger le processus. Les comportements par défaut de la plupart des signaux sont "ne rien faire" ou "mettre fin à ce processus et peut-être aussi produire un vidage de la base". SIGSEGV
est l'un de ces derniers.
Donc, pour récapituler, nous avons un processus qui a brisé les règles de protection de la mémoire. La CPU a suspendu le processus et généré une interruption synchrone. Le noyau a mis en place cette interruption et généré un SIGSEGV
signal pour le processus. Supposons que le processus n'ait pas configuré de gestionnaire de signal SIGSEGV
, le noyau applique donc le comportement par défaut, qui consiste à mettre fin au processus. Cela a tous les mêmes effets que l' _exit
appel système: les fichiers ouverts sont fermés, la mémoire est désallouée, etc.
Jusque-là, rien n'a encore imprimé de message visible par un humain, et le shell (ou plus généralement, le processus parent du processus qui vient d'être terminé) n'a pas été impliqué du tout. SIGSEGV
va au processus qui a enfreint les règles, pas son parent. L' étape suivante de la séquence consiste toutefois à informer le processus parent que son enfant a été arrêté. Cela peut se produire de plusieurs façons différentes, dont la plus simple est quand le parent attend déjà cette notification, en utilisant l' un des wait
appels système ( wait
, waitpid
, wait4
, etc.). Dans ce cas, le noyau ne fera que renvoyer cet appel système et fournira au processus parent un numéro de code appelé statut de sortie.. 7 Le statut de sortie indique au parent pourquoi le processus enfant a été arrêté. dans ce cas, il apprendra que l'enfant a été arrêté en raison du comportement par défaut d'un SIGSEGV
signal.
Le processus parent peut ensuite signaler l'événement à un humain en imprimant un message. les programmes shell le font presque toujours. Votre crsh
code n'inclut pas cela, mais cela arrive quand même, parce que la routine de la bibliothèque C system
exécute un shell complet /bin/sh
, "sous le capot". crsh
est le grand - parent dans ce scénario; la notification de processus parent est remplie par /bin/sh
, ce qui affiche son message habituel. Ensuite, /bin/sh
elle quitte elle-même, car elle n'a plus rien à faire, et l'implémentation de la bibliothèque C de system
reçoit cette notification de sortie. Vous pouvez voir cette notification de sortie dans votre code en inspectant la valeur de retour desystem
; mais cela ne vous dira pas que le processus de petit-enfant est mort sur un segfault, car il a été consommé par le processus de shell intermédiaire.
Notes de bas de page
Certains systèmes d'exploitation n'implémentent pas les pilotes de périphérique dans le noyau; Cependant, tous les gestionnaires d'interruptions doivent toujours faire partie du noyau, de même que le code qui configure la protection de la mémoire, car le matériel ne permet rien d'autre que le noyau de faire cela.
Il peut exister un programme appelé "hyperviseur" ou "gestionnaire de machine virtuelle" encore plus privilégié que le noyau, mais aux fins de cette réponse, il peut être considéré comme faisant partie du matériel .
Le noyau est un programme , mais ce n'est pas un processus. cela ressemble plus à une bibliothèque. Tous les processus exécutent des parties du code du noyau, de temps en temps, en plus de leur propre code. Il peut y avoir un certain nombre de "threads du noyau" qui n'exécutent que le code du noyau, mais ils ne nous concernent pas ici.
Le seul et unique système d'exploitation que vous aurez probablement à traiter et qui ne peut pas être considéré comme une implémentation d'Unix est bien entendu Windows. Il n'utilise pas de signaux dans cette situation. ( En effet, il n'a pas avoir des signaux, sous Windows l' <signal.h>
interface est complètement truqué par la bibliothèque C.) Il utilise ce qu'on appelle « gestion structurée des exceptions » au lieu.
Certaines violations de la protection de la mémoire génèrent SIGBUS
("Erreur de bus") au lieu de SIGSEGV
. La ligne entre les deux est sous-spécifiée et varie d'un système à l'autre. Si vous avez écrit un programme définissant un gestionnaire SIGSEGV
, c’est probablement une bonne idée de définir le même gestionnaire SIGBUS
.
"Erreur de segmentation" est le nom de l'interruption générée pour les violations de protection de la mémoire par l'un des ordinateurs qui exécutaient le système Unix d'origine , probablement le PDP-11 . La " segmentation " est un type de protection de la mémoire, mais de nos jours le terme " erreur de segmentation " désigne de manière générique toute violation de la protection de la mémoire.
Tous les autres moyens par lesquels le processus parent peut être averti qu'un enfant s'est terminé, aboutissent avec l'appel du parent wait
et la réception d'un statut de sortie. C'est juste que quelque chose d'autre se passe en premier.
crsh
est une excellente idée pour ce genre d’expérimentation. Merci de nous avoir tous informés de cette idée et de son idée.