Comment fonctionnent les signaux en interne?

31

En général, pour tuer les processus, nous générons des signaux comme SIGKILL, SIGTSTPetc.

Mais comment sait-on qui a commandé ce signal particulier, qui l'a envoyé à un processus particulier et, en général, comment les signaux effectuent-ils leurs opérations? Comment fonctionnent les signaux en interne?

Varun Chhangani
la source
La question est un peu difficile à comprendre. Je m'excuse et ne signifie aucun manque de respect. Voulez-vous savoir qui a pu exécuter une commande qui a tué un processus ou voulez-vous en savoir plus sur SIGKILL et SIGSTP?
pullsumo
@mistermister Je veux savoir qui a pu exécuter une commande qui a tué un processus et comment?
Varun Chhangani

Réponses:

35

La vue de 50 000 pieds est la suivante:

  1. Un signal est généré soit par le noyau en interne (par exemple, SIGSEGVlorsqu'une adresse non valide est accédée, ou SIGQUITlorsque vous appuyez sur Ctrl+ \), soit par un programme utilisant l' killappel système (ou plusieurs associés).

  2. Si c'est par l'un des appels système, le noyau confirme que le processus appelant dispose de privilèges suffisants pour envoyer le signal. Sinon, une erreur est renvoyée (et le signal ne se produit pas).

  3. S'il s'agit de l'un des deux signaux spéciaux, le noyau agit inconditionnellement sur lui, sans aucune entrée du processus cible. Les deux signaux spéciaux sont SIGKILL et SIGSTOP. Tous les éléments ci-dessous concernant les actions par défaut, les signaux de blocage, etc., ne sont pas pertinents pour ces deux.

  4. Ensuite, le noyau comprend ce que fait le signal:

    1. Pour chaque processus, une action est associée à chaque signal. Il y a un tas de valeurs par défaut et les programmes peuvent définir différents ceux utilisant sigaction, signaletc. Ceux - ci comprennent des choses comme « l' ignorer complètement », « tuer le processus », « tuer le processus avec une décharge de base », « arrêter le processus », etc.

    2. Les programmes peuvent également désactiver la livraison de signaux ("bloqués"), signal par signal. Ensuite, le signal reste en attente jusqu'à ce qu'il soit débloqué.

    3. Les programmes peuvent demander qu'au lieu que le noyau entreprenne lui-même une action, il envoie le signal au processus de manière synchrone (avec sigwait, et al. Ou signalfd) ou de manière asynchrone (en interrompant ce que fait le processus et en appelant une fonction spécifiée).

Il existe un deuxième ensemble de signaux appelés "signaux en temps réel", qui n'ont pas de signification spécifique, et permettent également la mise en file d'attente de plusieurs signaux (les signaux normaux n'attendent qu'un seul de chacun lorsque le signal est bloqué). Ils sont utilisés dans les programmes multithreads pour que les threads communiquent entre eux. Plusieurs sont utilisés dans l'implémentation des threads POSIX de la glibc, par exemple. Ils peuvent également être utilisés pour communiquer entre différents processus (par exemple, vous pouvez utiliser plusieurs signaux en temps réel pour qu'un programme fooctl envoie un message au démon foo).

Pour une vue de moins de 50 000 pieds, essayez man 7 signalégalement la documentation interne du noyau (ou source).

derobert
la source
"Les deux signaux spéciaux sont SIGKILL et SIGSTOP" alors quel peut être SIGCONT ...
Hauke ​​Laging
@HaukeLaging SIGCONT est le signal qui annule SIGSTOP. La documentation ne le répertorie pas comme spécial ... Je ne suis donc pas sûr si techniquement un processus peut le définir pour ignorer, alors vous ne pourrez pas le reprendre (seulement le SIGKILL).
derobert
22

L'implémentation du signal est très complexe et est spécifique au noyau. En d'autres termes, différents noyaux implémenteront les signaux différemment. Une explication simplifiée est la suivante:

La CPU, basée sur une valeur de registre spéciale, a une adresse en mémoire où elle s'attend à trouver une "table descripteur d'interruption" qui est en fait une table vectorielle. Il existe un vecteur pour chaque exception possible, comme la division par zéro, ou trap, comme INT 3 (débogage). Lorsque le CPU rencontre l'exception, il enregistre les drapeaux et le pointeur d'instruction en cours sur la pile, puis saute à l'adresse spécifiée par le vecteur correspondant. Sous Linux, ce vecteur pointe toujours vers le noyau, où se trouve un gestionnaire d'exceptions. Le CPU est maintenant terminé et le noyau Linux prend le relais.

Notez que vous pouvez également déclencher une exception à partir du logiciel. Par exemple, l'utilisateur appuie sur CTRL-C , puis cet appel va au noyau qui appelle son propre gestionnaire d'exceptions. En général, il existe différentes manières d'accéder au gestionnaire, mais quelle que soit la même chose de base: le contexte est enregistré sur la pile et le gestionnaire d'exceptions du noyau est sauté.

Le gestionnaire d'exceptions décide ensuite quel thread doit recevoir le signal. Si quelque chose comme la division par zéro s'est produit, alors c'est facile: le thread qui a provoqué l'exception reçoit le signal, mais pour d'autres types de signaux, la décision peut être très complexe et dans certains cas inhabituels, un thread plus ou moins aléatoire pourrait obtenir le signal.

Pour envoyer le signal, ce que fait le noyau, c'est d'abord définir une valeur indiquant le type de signal, SIGHUPou autre chose. Ce n'est qu'un entier. Chaque processus a une zone de mémoire "signal en attente" où cette valeur est stockée. Ensuite, le noyau crée une structure de données avec les informations de signal. Cette structure comprend une "disposition" de signal qui peut être par défaut, ignorer ou gérer. Le noyau appelle alors sa propre fonction do_signal(). La phase suivante commence.

do_signal()décide d' abord si elle traitera le signal. Par exemple, si c'est une mise à mort , alors do_signal()tue simplement le processus, fin de l'histoire. Sinon, il examine la disposition. Si la disposition est par défaut, do_signal()traite le signal selon une politique par défaut qui dépend du signal. Si la disposition est gérée, cela signifie qu'il y a une fonction dans le programme utilisateur qui est conçue pour gérer le signal en question et le pointeur vers cette fonction sera dans la structure de données susmentionnée. Dans ce cas, do_signal () appelle une autre fonction du noyau,handle_signal() , qui passe ensuite par le processus de retour en mode utilisateur et d'appel de cette fonction. Les détails de ce transfert sont extrêmement complexes. Ce code dans votre programme est généralement lié automatiquement à votre programme lorsque vous utilisez les fonctions de signal.h.

En examinant correctement la valeur du signal en attente, le noyau peut déterminer si le processus gère tous les signaux, et prendra les mesures appropriées dans le cas contraire, ce qui pourrait mettre le processus en veille ou le tuer ou toute autre action, selon le signal.

Tyler Durden
la source
15

Bien que cette question ait été répondue, permettez-moi de publier un flux détaillé des événements dans le noyau Linux.
Ceci est entièrement copié à partir des publications Linux: Linux Signals - Internals sur le blog "Linux posts" à sklinuxblog.blogspot.in.

Programme Espace Utilisateur Signal C

Commençons par écrire un programme C de l'espace utilisateur de signal simple:

#include<signal.h>
#include<stdio.h>

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

Ce code attribue un nouveau gestionnaire pour le signal SIGINT. SIGINT peut être envoyé au processus en cours en utilisant la combinaison de touches Ctrl+ C. Lorsque vous appuyez sur Ctrl+, Cle signal asynchrone SIGINT est envoyé à la tâche. Cela équivaut également à envoyer la kill -INT <pid>commande dans un autre terminal.

Si vous faites un kill -l(c'est un minuscule L, qui signifie «liste»), vous connaîtrez les différents signaux qui peuvent être envoyés à un processus en cours.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

La combinaison de touches suivante peut également être utilisée pour envoyer des signaux particuliers:

  • Ctrl+ C- envoie SIGINT quelle action par défaut est de mettre fin à l'application.
  • Ctrl+ \  - envoie à SIGQUIT quelle action par défaut consiste à mettre fin au noyau de vidage de l'application.
  • Ctrl+ Z- envoie SIGSTOP qui suspend le programme.

Si vous compilez et exécutez le programme C ci-dessus, vous obtiendrez la sortie suivante:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Même avec Ctrl+ Cou kill -2 <pid>le processus ne se terminera pas. Au lieu de cela, il exécutera le gestionnaire de signaux et reviendra.

Comment le signal est envoyé au processus

Si nous voyons les internes du signal envoyés à un processus et mettons Jprobe avec dump_stack à la __send_signalfonction, nous verrons la trace d'appel suivante:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

La fonction principale appelle donc l'envoi du signal:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Maintenant, tout est mis en place et les modifications nécessaires sont apportées task_structau processus.

Traitement du signal

Le signal est vérifié / géré par un processus lorsqu'il revient d'un appel système ou si le retour d'une interruption est effectué. Le retour de l'appel système est présent dans le fichier entry_64.S.

La fonction int_signal est appelée à partir de entry_64.Slaquelle la fonction est appelée do_notify_resume().

Vérifions la fonction do_notify_resume(). Cette fonction vérifie si le TIF_SIGPENDINGdrapeau est défini dans task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

Appels et signaux SYSTEM

Les appels système «lents», par exemple bloquer la lecture / écriture, mettre les processus en attente: TASK_INTERRUPTIBLEou TASK_UNINTERRUPTIBLE.

Une tâche en état TASK_INTERRUPTIBLEsera changée en TASK_RUNNINGétat par un signal. TASK_RUNNINGsignifie qu'un processus peut être planifié.

S'il est exécuté, son gestionnaire de signaux sera exécuté avant la fin de l'appel système «lent». Le syscallne se termine pas par défaut.

Si l' SA_RESTARTindicateur est défini, syscallest redémarré une fois le gestionnaire de signaux terminé.

Les références

K_K
la source
Merci de faire un effort pour contribuer au site, mais (1) si vous allez copier du matériel d'un autre site (mot pour mot, lettre pour lettre, y compris les erreurs de grammaire et de ponctuation), vous devez dire que vous faites donc, beaucoup plus clairement. L'inscription de la source comme «référence», bien que nécessaire, n'est pas suffisante. Sauf si vous êtes l'auteur du blog (K_K = sk?), Auquel cas vous n'êtes pas obligé de vous y connecter - mais, si vous le faites, vous devez divulguer (c'est-à-dire dire) qu'il vous appartient. … (Suite)
G-Man dit «Réintègre Monica» le
(Suite)… (2) Votre source (le blog que vous avez copié) n'est pas très bonne. Cela fait quatre ans que la question a été posée; n'avez-vous pas pu trouver une meilleure référence à partir de laquelle copier? (Si vous êtes l'auteur d'origine, désolé.) En plus des erreurs de grammaire et de ponctuation susmentionnées (et d'une formulation généralement bâclée et d'une mise en forme médiocre), c'est faux. (2a) Ctrl + Z envoie SIGTSTP, pas SIGSTOP. (SIGTSTP, comme SIGTERM, peut être attrapé; SIGSTOP, comme SIGKILL, ne peut pas.)… (Suite)
G-Man dit 'Reinstate Monica'
(Suite)… (2b) Le shell n'envoie pas le signal Ctrl + C. Le shell n'a aucun rôle dans l'envoi de signaux (sauf lorsque l'utilisateur utilise la killcommande, qui est un shell intégré). (2c) Si les points-virgules après la fermeture }d'une fonction ne sont pas à proprement parler des erreurs, ils sont inutiles et peu orthodoxes. (3) Même si tout était correct, ce ne serait pas une très bonne réponse à la question. (3a) La question, quoique quelque peu floue, semble se concentrer sur la façon dont les acteurs (utilisateurs et processus) initient (c'est-à-dire envoient ) des signaux. … (Suite)
G-Man dit «Réintègre Monica» le
(Suite)… La réponse semble se concentrer sur les signaux générés par le noyau (en particulier, les signaux générés par le clavier) et la façon dont le processus destinataire réagit aux signaux. (3b) La question semble être au niveau de «Quelqu'un a tué mon processus - qui l'a fait et comment?» La réponse traite de l'API de gestion du signal, des routines du noyau, du débogage du noyau (Jprobe?), Des traces de pile du noyau et structures de données du noyau. OMI, qui est de bas niveau de manière inappropriée - d'autant plus qu'il ne fournit aucune référence où un lecteur pourrait en savoir plus sur ces rouages ​​internes.
G-Man dit `` Réintègre Monica '' le
1
C'est mon propre blog .. mes propres traces .. c'est ce que je veux .. tout le monde connaîtra un tel flux détaillé .. parler dans l'air n'a aucun sens .. même si après avoir violé les directives de cette communauté, veuillez retirer ma réponse par le biais de la bonne canal .. c'est la réponse interne du noyau et non les internes de la grammaire.
K_K