Comment fonctionnent les signaux Unix?

Comment fonctionnent les signaux sous Unix? Je suis passé par WR Stevens mais j’ai été incapable de comprendre. Aidez-moi, s’il vous plaît.

L’explication ci-dessous n’est pas exacte et plusieurs aspects de son fonctionnement diffèrent d’un système à l’autre (et peut-être même du même système d’exploitation sur un matériel différent pour certaines parties), mais je pense qu’il est généralement suffisant pour satisfaire votre curiosité. Utilise les. La plupart des gens commencent à utiliser des signaux dans la programmation sans même avoir ce niveau de compréhension, mais avant de les utiliser, je voulais les comprendre.

livraison du signal

Le kernel du système d’exploitation dispose d’une structure de données appelée bloc de contrôle de processus pour chaque processus en cours d’exécution qui contient des données sur ce processus. Cela peut être recherché par l’identifiant de processus (PID) et comprend un tableau des actions de signal et des signaux en attente.

Lorsqu’un signal est envoyé à un processus, le kernel du système d’exploitation recherche le bloc de contrôle de processus de ce processus et examine la table d’action du signal pour localiser l’action pour le signal particulier envoyé. Si la valeur de l’action du signal est SIG_IGN le kernel oublie le nouveau signal. Si la valeur de l’action du signal est SIG_DFL le kernel recherche l’action de traitement du signal par défaut pour ce signal dans une autre table et réalise cette action. Si les valeurs sont quelque chose d’autre, cela est supposé être une adresse de fonction dans le processus auquel le signal est envoyé et qui devrait être appelé. Les valeurs pour SIG_IGN et SIG_DFL sont des nombres SIG_DFL en pointeurs de fonctions dont les valeurs ne sont pas des adresses valides dans l’espace adresse d’un processus (telles que 0 et 1, qui sont toutes deux à la page 0, jamais associées à un processus).

Si une fonction de traitement du signal a été enregistrée par le processus (la valeur de l’action de signal n’était ni SIG_IGN ni SIG_DFL), une entrée dans la table de signal en attente est faite pour ce signal et ce processus est marqué quelque chose, comme des données à rendre disponibles pour un appel à read , à attendre un signal ou plusieurs autres choses).

Maintenant, la prochaine fois que le processus sera exécuté, le kernel du système d’exploitation appenda d’abord des données à la stack et modifiera le pointeur d’instruction de ce processus afin qu’il ressemble presque au processus lui-même. Ce n’est pas tout à fait correct et s’écarte assez de ce qui se passe réellement pour en parler plus en détail.

La fonction de gestionnaire de signaux peut faire tout ce qu’elle fait (cela fait partie du processus pour lequel elle a été appelée, elle a donc été écrite avec des connaissances sur ce que ce programme devrait faire avec ce signal). Lorsque le gestionnaire de signaux retourne, le code normal du processus recommence à s’exécuter. (encore une fois, pas précis, mais plus à ce sujet ensuite)

Ok, ce qui précède devrait vous donner une bonne idée de la manière dont les signaux sont transmis à un processus. Je pense que cette bonne version de cette idée est nécessaire avant de pouvoir saisir l’idée complète, qui inclut des choses plus compliquées.

Très souvent, le kernel du système d’exploitation doit savoir quand un gestionnaire de signaux retourne. Cela est dû au fait que les gestionnaires de signaux prennent un argument (qui peut nécessiter un espace de stack), vous pouvez bloquer le même signal deux fois pendant l’exécution du gestionnaire de signaux et / ou faire redémarrer les appels système après la transmission du signal. Pour accomplir ceci un peu plus que les changements de pointeur de stack et d’instruction.

Ce qui doit arriver, c’est que le kernel ait besoin de faire en sorte que le processus lui dise qu’il a fini d’exécuter la fonction de gestionnaire de signaux. Cela peut être fait en mappant une section de RAM dans l’espace d’adressage du processus qui contient du code pour effectuer cet appel système et l’adresse du retour pour la fonction de gestionnaire de signaux (la valeur supérieure de la stack lorsque cette fonction est en cours d’exécution). ce code. Je pense que c’est comme ça que ça se passe sous Linux (au moins les versions les plus récentes). Une autre façon d’y parvenir (je ne sais pas si cela est fait, mais cela pourrait être) serait de faire en sorte que l’adresse de retour de la fonction de gestionnaire de signal soit une adresse invalide (telle que NULL) qui provoquerait une interruption sur la plupart des systèmes. , ce qui donnerait à nouveau le contrôle du kernel du système d’exploitation. Ce n’est pas grave, mais le kernel doit reprendre le contrôle pour réparer la stack et savoir que le gestionnaire de signal est terminé.

Tandis que je cherchais une autre question, j’ai appris

que le kernel Linux mappe une page dans le processus pour cela, mais que l’appel système réel pour enregistrer les gestionnaires de signaux (quels appels sigaction) prend un paramètre paramètre sa_restore, qui doit être utilisé comme adresse de retour du signal gestionnaire, et le kernel s’assure juste qu’il est mis là. Le code à cette adresse émet l’appel système ( sigreturn ) et le kernel sait que le gestionnaire de signal est terminé.

génération de signal

Je suppose surtout que vous savez comment les signaux sont générés en premier lieu. Le système d’exploitation peut les générer pour le compte d’un processus à cause de quelque chose, comme une temporisation expirant, un processus enfant mourant, accéder à la mémoire à laquelle il ne devrait pas accéder ou émettre une instruction qu’il ne doit pas ou celui qui est privilégié), ou beaucoup d’autres choses. Le temporisateur est fonctionnellement un peu différent des autres car il peut se produire lorsque le processus n’est pas en cours d’exécution, et ressemble donc davantage aux signaux envoyés avec l’appel du système de mise à kill . Pour les signaux non liés à la temporisation envoyés au nom du processus en cours, ils sont générés lorsqu’une interruption se produit car le processus en cours fait quelque chose de mal. Cette interruption donne le contrôle du kernel (tout comme un appel système) et le kernel génère le signal à transmettre au processus en cours.

Considérez la fonction de signal comme des interruptions, implémentées par le système d’exploitation (plutôt que dans le matériel).

Alors que votre programme traverse joyeusement son locus d’exécution rooté dans main (), ces interruptions peuvent se produire, entraîner l’envoi du programme sur un vecteur (gestionnaire), y exécuter le code, puis retourner à l’emplacement où il a été interrompu.

Ces interruptions (signaux) peuvent provenir de diverses sources, par exemple des erreurs matérielles telles que l’access à des adresses incorrectes ou mal alignées, la mort d’un processus enfant, des signaux générés par l’utilisateur à l’aide de la commande kill ou d’autres processus utilisant l’appel système kill . La façon dont vous consumz des signaux consiste à désigner des gestionnaires qui sont dissortingbués par le système d’exploitation lorsque les signaux se produisent. Notez que certains de ces signaux ne peuvent pas être manipulés, ce qui entraîne simplement la mort du processus.

Mais ceux qui peuvent être manipulés peuvent être très utiles. Vous pouvez les utiliser pour la communication inter-processus, c’est-à-dire qu’un processus envoie un signal à un autre processus, qui le gère, et dans le gestionnaire, il fait quelque chose d’utile. De nombreux démons font des choses utiles comme relire le fichier de configuration si vous leur envoyez le bon signal.

Certains problèmes qui ne sont pas traités dans toutes les instructions ci-dessus sont multi-core, s’exécutant dans l’espace kernel tout en recevant un signal, dormant dans l’espace kernel tout en recevant un signal, redémarrage de l’appel système et latence du gestionnaire de signal.

Voici quelques points à considérer:

  • Que se passe-t-il si le kernel sait qu’un signal doit être transmis au processus X qui s’exécute sur CPU_X, mais que le kernel l’apprend lors de l’exécution sur CPU_Y (CPU_X! = CPU_Y). Le kernel doit donc empêcher le processus de s’exécuter sur un autre kernel.
  • Que se passe-t-il si le processus s’exécute dans l’espace du kernel lors de la réception d’un signal? Chaque fois qu’un processus effectue un appel système, il entre dans l’espace du kernel et bricole avec les structures de données et les allocations de mémoire dans l’espace du kernel. Est-ce que tout ce piratage a également lieu dans l’espace kernel?
  • Que se passe-t-il si le processus est en train de dormir dans l’espace kernel en attente d’un autre événement? (read, write, signal, poll, mutex ne sont que quelques options).

Réponses:

  • Si le processus est en cours d’exécution sur un autre processeur, le kernel, via une communication croisée entre processeurs, fournira une interruption à l’autre CPU et un message pour celui-ci. L’autre CPU va, en matériel, enregistrer l’état et passer au kernel sur l’autre CPU, puis effectuer la livraison du signal sur l’autre CPU. Tout cela fait partie de la tentative de ne pas exécuter le gestionnaire de signal du processus sur une autre CPU qui cassera la localité du cache.
  • Si le processus s’exécute dans l’espace kernel, il n’est pas interrompu. Au lieu de cela, il est enregistré que ce processus a reçu un signal. Lorsque le processus quitte l’espace kernel (à la fin de chaque appel système), le kernel configure le trampoline pour exécuter le gestionnaire de signal.
  • Si le processus, en cours d’exécution dans l’espace du kernel, après avoir reçu un signal, atteint une fonction de veille, alors cette fonction de veille (commune à toutes les fonctions de veille du kernel) vérifie si le processus a un signal en attente. Si tel est le cas, cela ne mettra pas le processus en veille et annulera à la place tout ce qui a été fait dans le kernel, et quittera l’espace utilisateur lors de la configuration d’un trampoline pour exécuter le gestionnaire de signaux puis redémarrer le système. appel. Vous pouvez réellement contrôler quels signaux vous souhaitez interrompre les appels système et que vous n’utilisez pas l’appel système siginterrupt(2) . Vous pouvez décider si vous souhaitez que les appels système puissent être redémarrés pour un certain signal lorsque vous enregistrez le signal en utilisant sigaction(2) avec le drapeau SA_RESTART . Si un appel système est émis et est interrompu par un signal et n’est pas redémarré automatiquement, vous obtenez une valeur de retour EINTR (interrompue) et vous devez gérer cette valeur. Vous pouvez également consulter l’appel système restart_syscall(2) pour plus de détails.
  • Si le processus est déjà en train de dormir ou d’attendre dans l’espace du kernel (en fait, tout le sumil est en attente), le code du kernel se nettoie et passe directement au gestionnaire de signal. L’appel est automatiquement redémarré si l’utilisateur le souhaite (très similaire à l’explication précédente de ce qui se passe si le processus s’exécute dans l’espace du kernel).

Quelques notes expliquant pourquoi tout cela est si complexe:

  • Vous ne pouvez pas simplement arrêter un processus qui s’exécute dans l’espace du kernel, car le développeur du kernel alloue de la mémoire, effectue des opérations sur les structures de données, etc. Si vous retirez simplement le contrôle, vous allez corrompre l’état du kernel et provoquer un blocage de la machine. Le code du kernel doit être notifié de manière contrôlée pour qu’il arrête son exécution, revienne dans l’espace utilisateur et laisse l’espace utilisateur gérer le signal. Cela se fait via la valeur de retour de toutes les fonctions de veille (enfin presque toutes) du kernel. Et les programmeurs du kernel doivent traiter ces valeurs de retour avec respect et agir en conséquence.
  • Les signaux sont asynchrones. Cela signifie qu’ils doivent être livrés dès que possible. Imaginez un processus qui n’a qu’un seul thread, s’endort pendant une heure et reçoit un signal. Le sumil est à l’intérieur du kernel. Donc, à part le code du kernel, vous devez vous réveiller, nettoyer après lui-même, retourner à l’espace utilisateur et exécuter le gestionnaire de signaux, éventuellement en redémarrant l’appel système une fois le gestionnaire de signaux terminé. Vous ne vous attendez certainement pas à ce que le processus exécute le gestionnaire de signaux une heure plus tard. Ensuite, vous vous attendez à ce que le sumil reprenne. L’espace utilisateur et les utilisateurs du kernel ont beaucoup de mal à accepter cela.
  • Dans l’ensemble, les signaux sont comme les gestionnaires d’interruption, mais pour l’espace utilisateur. C’est une bonne analogie mais pas parfaite. Bien que les gestionnaires d’interruption soient générés par le matériel, certains gestionnaires de signaux proviennent du matériel, mais la plupart ne sont que des logiciels (signal d’un processus enfant mourant, signal d’un autre processus utilisant l’appel système kill(2) et plus).

Alors, quelle est la latence de la gestion du signal?

  • Si, au moment où vous recevez un signal, un autre processus est en cours d’exécution jusqu’au planificateur du kernel pour décider si l’autre processus doit terminer sa tranche de temps et ensuite seulement délivrer le signal. Si vous utilisez un système Linux / Unix standard, cela signifie que vous pouvez être retardé d’une ou plusieurs tranches de temps avant d’obtenir le signal (ce qui signifie des millisecondes équivalentes à l’éternité).
  • Lorsque vous recevez un signal, si votre processus est hautement prioritaire ou si d’autres processus ont déjà leur tranche de temps, vous obtiendrez le signal assez rapidement. Si vous exécutez dans l’espace utilisateur, vous l’obtiendrez “immédiatement”, si vous exécutez dans l’espace du kernel, vous atteindrez rapidement une fonction de veille ou de retour du kernel, auquel cas le gestionnaire de signaux sera appelé. C’est généralement un court laps de temps car le kernel ne passe pas beaucoup de temps.
  • Si vous dormez dans le kernel, et que rien d’autre n’est au dessus de votre priorité ou ne doit s’exécuter, le thread du kernel qui gère votre appel système est réveillé, nettoie après tout ce qu’il a fait dans le kernel. espace utilisateur et exécute votre signal. Cela ne prend pas trop de temps (parlaient des microsecondes ici).
  • Si vous exécutez une version en temps réel de Linux et que votre processus a la priorité en temps réel la plus élevée, vous recevrez le signal très rapidement après son déclenchement. On parle de 50 microsecondes ou même mieux (cela dépend d’autres facteurs que je ne peux pas utiliser).

Le signal n’est qu’une interruption dans l’exécution du processus. Un processus peut se signaler ou provoquer la transmission d’un signal à un autre processus. Peut-être qu’un parent peut envoyer un signal à son enfant pour le terminer, etc.

Cochez le lien suivant pour comprendre.

https://unix.stackexchange.com/questions/80044/how-signals-work-internally

http://www.linuxjournal.com/article/3985

http://www.linuxprogrammingblog.com/all-about-linux-signals?page=show