Cet article résume mes recherches sur les bases et la mécanique des signaux Linux (implémentation dans le noyau).
J'utilise habituellement des signaux, mais je ne comprenais pas comment ils fonctionnent, alors je les ai recherchés comme sujet d'étude du noyau. C'était plus compliqué et volumineux que je ne l'avais imaginé, donc il y a certaines parties que je ne pouvais pas écrire (parties que je ne pouvais pas étudier), mais je pense avoir compris le flux général (mécanisme).
Cet article est principalement composé de "■ Basic Edition" et "■ Kernel Edition (v5.5)". Je pense qu'il est nécessaire de connaître les bases pour comprendre le mécanisme, donc il est structuré comme ça. Il est recommandé à ceux qui comprennent les bases du niveau livre de lire "[■ Kernel Edition (v5.5)](# -Kernel Edition-v55)".
Tout d'abord, jetons un coup d'œil aux bases des signaux.
L'utilisation détaillée des commandes et de l'API (langage C) et la gestion des erreurs qui apparaissent dans les exemples sont omises. Pour plus de détails, veuillez utiliser les informations en man et les références.
Une fonction du noyau qui notifie les processus et les groupes de processus de divers événements (interruption logicielle). Les notifications d'événements peuvent être envoyées à partir de divers emplacements (vos / autres processus, noyau), notamment:
Le signal est traité dans le flux suivant. Nous examinerons les détails plus tard.
Pour faciliter l'imagination, voici trois exemples d'utilisation du signal du point de vue de l'utilisateur.
La commande kill envoie un signal (SIGTERM) pour terminer le processus de mise en veille.
$ ps
PID TTY TIME CMD
2662 pts/0 00:00:00 bash
2679 pts/0 00:00:00 sleep
2685 pts/0 00:00:00 ps
$ kill 2679
[1]+ Terminated sleep 1000
$ ps
PID TTY TIME CMD
2662 pts/0 00:00:00 bash
2696 pts/0 00:00:00 ps
La touche d'interruption (Ctrl + C) envoie un signal (SIGINT) pour terminer la commande de boucle infinie (groupe de processus).
$ while :; do sleep 1; echo "Stop me !"; done
Stop me !
Stop me !
Stop me !
^C
Vous pouvez également envoyer un signal (SIGTERM) dans votre propre programme (send_sig.c) pour terminer le processus de veille comme suit:
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = atoi(argv[1]);
kill(pid, SIGTERM);
return 0;
}
Exemple de compilation et d'exécution
$ ps
PID TTY TIME CMD
30285 pts/0 00:00:00 bash
30597 pts/0 00:00:00 sleep
30631 pts/0 00:00:00 ps
$ gcc -o send_sig send_sig.c
$ ./send_sig 30597
[1]+ Terminated sleep 1000
$ ps
PID TTY TIME CMD
30285 pts/0 00:00:00 bash
30663 pts/0 00:00:00 ps
Les signaux sont numérotés et nommés en fonction de leur utilisation prévue.
$ 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
Cependant, certains numéros de signaux dépendent de l'architecture et peuvent différer de l'exemple ci-dessus (x86_64) [^ 7signal].
[Signal ^ 7]: Voir signal man 7
pour plus de détails.
Les signaux peuvent être largement divisés en deux types: les signaux standard et les signaux en temps réel.
La différence approximative est
--Signal standard ... Signaux traditionnellement utilisés tels que SIGKILL (1 à 31)
est. Les différences légèrement plus subtiles sont:
Signal standard | Signal en temps réel | |
---|---|---|
Nouveau/Vieux | Vieux | Nouveau |
Nom du signal | Divers pour chaque signal | SIGRTMIN(+n), SIGRTMAX(-n) |
Numéro de signal | 1 〜 31 | 32 〜 64 |
Fonctionnement standard(Les détails seront décrits plus tard) | Divers pour chaque signal | Fin du processus(Tous les signaux) |
Utilisation | Divers pour chaque signal | Défini par l'utilisateur(Tous les signaux) |
Plusieurs mêmes signaux Comportement au moment de la réception |
Recevez un seul | Tout recevoir |
Plusieurs mêmes signaux Commande lors de l'envoi |
Pas de réglementation | Arrivé dans l'ordre d'envoi |
Plusieurs signaux différents Commande lors de l'envoi |
Pas de réglementation | Arrivé dans l'ordre croissant du numéro de signal |
Cet article ne va pas plus loin dans les signaux en temps réel [^ 7signal].
Il existe trois types d'actions (actions de signal) lors de la réception d'un signal. Ce comportement peut être modifié à partir de sigaction () décrit plus tard. Cependant, ** SIGKILL et SIGSTOP ne peuvent être utilisés qu'en fonctionnement standard. ** **
Il ne fait rien lorsqu'il reçoit un signal.
(Pour définir, spécifiez SIG_IGN pour sa_handler avec sigaction () décrit plus loin)
Lorsqu'un signal est reçu, l'opération standard définie pour chaque signal (Term, Ign, Core, Stop, Cont décrit plus loin) est exécutée.
(Pour définir, spécifiez SIG_DFL pour sa_handler avec sigaction () décrit plus loin (par défaut))
Lorsqu'il reçoit un signal, il exécute une action définie par l'utilisateur.
(Pour définir, spécifiez une fonction définie par l'utilisateur dans sa_handler avec sigaction () etc. décrit plus loin)
Le fonctionnement standard est défini pour chaque signal et il existe les cinq types suivants.
Nom de l'opération | sens |
---|---|
Term | Fin du processus |
Ign | ne fais rien(sigaction()SIG qui peut être réglé avec_Identique à IGN) |
Core | Terminaison de processus et génération de vidage de mémoire[^5core] |
Stop | Pause de processus(TASK_Transition à l'état STOPPED) |
Cont | Reprendre un processus suspendu(TASK_Retour de l'état STOPPED) |
Cependant, cela concerne uniquement les signaux standard. Terme uniquement pour les signaux en temps réel (voir figure ci-dessous).
[^ 5 core]: Voir man 5 core
pour plus de détails.
Les signaux standard 1 à 31 ont un comportement standard associé à chacun.
Nom du signal | Numéro de signal(x86_64) | Fonctionnement standard | sens |
---|---|---|---|
SIGHUP | 1 | Term | Détection de blocage du terminal de contrôle ou mort du processus de contrôle |
SIGINT | 2 | Term | Interruption du clavier(Ctrl + C) |
SIGQUIT | 3 | Core | Quitter le clavier(Ctrl + Q) |
SIGILL | 4 | Core | Commande illégale |
SIGTRAP | 5 | Core | Trace pour le débogage/Signal de point d'arrêt |
SIGABRT | 6 | Core | abort(3)Signal de suspension de |
SIGBUS | 7 | Core | Erreur de bus(Accès mémoire illégal) |
SIGFPE | 8 | Core | Exception à virgule flottante |
SIGKILL | 9 | Term | Tuer le signal |
SIGUSR1 | 10 | Term | Signal défini par l'utilisateur(Partie 1) |
SIGSEGV | 11 | Core | Référence mémoire illégale |
SIGUSR2 | 12 | Term | Signal défini par l'utilisateur(Partie 2) |
SIGPIPE | 13 | Term | Destruction de tuyaux(Exporter vers un tube sans lecteur) [^7pipe]。 |
SIGALRM | 14 | Term | alarm(2)Signal de minuterie de |
SIGTERM | 15 | Term | Signal de fin |
SIGSTKFLT | 16 | Term | Processeur arithmétique numérique(Coprocesseur)Défaut de pile dans(inutilisé) |
SIGCHLD | 17 | Ign | Suspendre le processus enfant(Reprendre)Ou finir |
SIGCONT | 18 | Cont | Reprendre le processus de pause |
SIGSTOP | 19 | Stop | Pause du processus |
SIGTSTP | 20 | Stop | Arrêt depuis le terminal de contrôle(Ctrl + Z) |
SIGTTIN | 21 | Stop | Terminal de contrôle de lecture de processus en arrière-plan |
SIGTTOU | 22 | Stop | Processus d'arrière-plan écrit sur le terminal de contrôle |
SIGURG | 23 | Ign | Des données hors bande et des données d'urgence existent dans le socket |
SIGXCPU | 24 | Core | Limite de temps CPU(RLIMIT_CPU)Au-delà[^2setrlimit]。 |
SIGXFSZ | 25 | Core | Limite de taille de fichier(RLIMIT_FSIZE)Au-delà[^2setrlimit]。 |
SIGVTALRM | 26 | Term | Minuterie virtuelle(Temps CPU en mode utilisateur pour le processus)Fin du temps |
SIGPROF | 27 | Term | Le temporisateur de profilage a expiré |
SIGWINCH | 28 | Ign | Modification de la taille de la fenêtre du terminal de contrôle |
SIGIO | 29 | Term | Asynchrone I/O événement |
SIGPWR | 30 | Term | Erreur d'alimentation |
SIGSYS | 31 | Core | Fait un appel système illégal[^2seccomp]。 |
[^ 7 pipe]: Voir man 7 pipe
pour plus de détails.
[^ 2 setrlimit]: Voir man 2 setrlimit
pour plus de détails.
[^ 2 seccomp]: Voir man 2 seccomp
pour plus de détails.
Pour modifier les actions de signal autres que ** SIGKILL ** et ** SIGSTOP **, utilisez l'API (langage C) comme la commande trap ou sigaction ().
trap est un type de commande intégrée intégrée à Bash qui vous permet de définir des actions de signal [^ 1bash].
Ici, essayez de régler (premier argument '') pour ne rien faire (ignorer le signal) lors de la réception de SIGINT.
[^ 1 bash]: Voir man 1 bash
pour plus de détails.
Exemple de réglage du signal à ignorer
$ trap '' SIGINT
Dans cet état, même si SIGINT est reçu, il ne répond pas, donc la touche d'interruption (Ctrl + C) ne fonctionne pas comme indiqué dans l'exemple d'exécution suivant.
$ while true; do sleep 1; echo "Stop me !"; done
Stop me !
Stop me !
^CStop me ! <--- Ctrl +L'appui sur C ne fonctionne pas et la commande continue
Stop me !
Stop me !
^Z <--- Ctrl +Appuyez sur Z pour arrêter(Transmission SIGTSTP)
[1]+ Stopped sleep 1
sigaction () est une API (langage C) qui permet de définir le comportement d'un signal [^ 2 sigaction].
Dans l'exemple de programme suivant (set_sigh.c), essayez de définir la fonction de gestionnaire à exécuter lorsque SIGINT est reçu.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
static void handler(int signo)
{
/*
*À l'origine, vous devriez utiliser des fonctions de sécurité du signal asynchrone dans les gestionnaires,
*Ici n'est pas printf()、exit()J'utilise une fonction telle que.
*À propos de la sécurité des signaux asynchrones$Voir le signal de l'homme 7.
*/
printf(" ... Caught the SIGINT (%d)\n", signo);
exit(EXIT_SUCCESS);
}
int main(void)
{
unsigned int sec = 1;
struct sigaction act;
//Handler lors de la réception de SIGINT()Prêt à courir.
memset(&act, 0, sizeof act);
act.sa_handler = handler;
sigaction(SIGINT, &act, NULL);
// Ctrl +Sortez un message toutes les secondes jusqu'à ce qu'il se termine par C etc.
for (;;) {
sleep(sec);
printf("Stop me !\n");
}
return 0;
}
Dans l'exemple d'exécution suivant, après avoir reçu SIGINT, handler () renvoie un message (... Caught the SIGINT (2)) et termine le programme.
$ gcc -o set_sigh set_sigh.c
$ ./set_sigh
Stop me !
Stop me !
Stop me !
^C ... Caught the SIGINT (2) <--- Ctrl +Appuyez sur C pour exécuter la fonction de gestionnaire
Une autre API est signal (), qui est obsolète pour la portabilité (ancienne API). Sauf si vous avez une raison spécifique, évitez de l'utiliser [signal ^ 2].
[^ 2 sigaction]: Voir man 2 sigaction
pour plus de détails.
[Signal ^ 2]: Voir signal homme 2
pour plus de détails.
Les signaux autres que ** SIGKILL et SIGSTOP ** peuvent être bloqués par processus.
Par exemple, si un processus reçoit un SIGINT, il sera normalement terminé par un comportement standard, mais s'il bloque le SIGINT, il ne répondra pas, comme ignorer un signal, quand il recevra le SIGINT.
La différence entre ignorer le signal est de savoir s'il faut ou non retenir le signal reçu. Si le signal est ignoré, le signal n'est pas maintenu, mais s'il s'agit d'un bloc de signal, il peut être maintenu. Par conséquent, une fois débloqué, il peut être traité sans avoir à recevoir à nouveau le signal [^ sigign].
[^ sigign]: Il peut être annulé même si le signal est ignoré, mais le signal reçu dans l'état de signal ignoré n'est pas maintenu, il ne sera donc pas traité même après avoir été annulé. Vous devez effacer l'ignorer et recevoir à nouveau le signal.
Utilisez une API (langage C) telle que sigprocmask () pour définir le bloc de signaux [^ 2 sigprocmask].
L'exemple de programme suivant (block_sig.c) essaie de bloquer SIGINT puis de débloquer SIGINT 5 secondes plus tard.
[^ 2 sigprocmask]: Voir man 2 sigprocmask
pour plus de détails.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(void)
{
unsigned int sec = 5;
sigset_t set;
//Vider le jeu de signaux et ajouter SIGINT
sigemptyset(&set);
sigaddset(&set, SIGINT);
//Bloquer SIGINT(en attente)Faire
sigprocmask(SIG_BLOCK, &set, NULL);
printf("Block the SIGINT for %d sec\n", sec);
sleep(sec);
//Débloquer SIGINT
printf("\nPassed %d sec, unblock the SIGINT\n", sec);
sigprocmask(SIG_UNBLOCK, &set, NULL);
printf("Done !\n");
return 0;
}
Dans l'exemple d'exécution, SIGINT est envoyé après le bloc SIGINT, mais vous pouvez voir que l'opération lors de la réception de SIGNINT est exécutée après la libération du bloc.
$ gcc -o block_sig block_sig.c
$ ./block_sig
Block the SIGINT for 5 sec
^C^C^C^C^C^C^C^C^C^C <--- Ctrl +Ne répond pas lorsque vous appuyez sur C
Passed 5 sec, unblock the SIGINT <---Puisqu'il est terminé par SIGINT, après cela"Done !"Il n'y a pas de sortie
Vous pouvez vérifier l'état tel que le bloc de signal (en attente) et ignorer de / proc / <PID> / status
.
$ cat /proc/29498/status
Name: bash
--- snip ---
SigQ: 0/29305 //Signal mis en file d'attente vers le véritable UID de ce processus/Limite supérieure du signal en file d'attente
SigPnd: 0000000000000000 //Processus spécifique(fil)Nombre de signaux en attente adressés
ShdPnd: 0000000000000000 //Nombre de signaux en attente destinés à l'ensemble du groupe de threads
SigBlk: 0000000000010000 //Signal à bloquer(Valeur du masque de bits)
SigIgn: 0000000000380004 //Signal ignoré(Valeur du masque de bits)
SigCgt: 000000004b817efb //Signal en attente d'être capturé(Valeur du masque de bits)
--- snip ---
Parmi ceux-ci, SigBlk, SigIgn et SigCgt sont un peu déroutants à lire car ils sont gérés par une valeur de masque appelée ** ensemble de signaux ** pour représenter plusieurs signaux ensemble. Par exemple, SigIgn (0000000000380004) a la signification suivante (convertir hexadécimal en binaire, déterminer le signal correspondant à partir de la position 1): En d'autres termes, SigIgn (signal ignoré) avec PID 29498 signifie quatre signaux correspondant à 3, 20, 21, 22.
0000000000380004 (hex) -> 1110000000000000000100 (bit)Numéro du nom du signal
||| *---------> SIGQUIT (03)
||*--------------------------> SIGTSTP (20)
|*---------------------------> SIGTTIN (21)
*----------------------------> SIGTTOU (22)
Vous pouvez également vérifier les mêmes informations avec ps s
.
$ ps s
UID PID PENDING BLOCKED IGNORED CAUGHT STAT TTY TIME COMMAND
1000 29498 0000000000000000 0000000000010000 0000000000380004 000000004b817efb Ss pts/0 0:00 /bin/bash
1000 29517 0000000000000000 0000000000010000 0000000000380004 000000004b817efb Ss pts/1 0:00 /bin/bash
1000 29572 0000000000000000 0000000000000000 0000000000003000 00000001ef804eff S+ pts/0 0:00 vim kernel/signal.c
1000 29581 0000000000000000 0000000000000000 0000000000000000 0000000000000000 S pts/1 0:00 sleep 100
1000 29588 0000000000000000 0000000000000000 0000000000000000 00000001f3d1fef9 R+ pts/1 0:00 ps s
À partir de là, nous parlons du point de vue du noyau. Jetons un coup d'œil au flux entre l'envoi d'un signal et le traitement de l'action du signal.
Examinons d'abord la structure des données. Une structure de données est une structure qui apparaît dans l'implémentation du noyau. De nombreuses structures sont impliquées dans les signaux, il est donc utile de les connaître d'abord pour aider votre code à comprendre.
Ensuite, regardons l'implémentation du noyau [^ kernel_ver]. Les signaux ont une configuration de traitement étape par étape dans l'ordre de la génération du signal (premier étage) et de la livraison du signal (deuxième étape) comme indiqué ci-dessous, alors jetons un coup d'œil à chacun.
--Génération du signal (1ère étape) ... Enregistrer la transmission du signal dans la structure de données du processus de destination (maintenir le signal) --Livraison du signal (deuxième étape) ... Effectuer l'action du signal en réponse au signal
[^ kernel_ver]: Comme mentionné dans le titre, la version du noyau utilise la v5.5, qui est la dernière au moment de l'enquête.
Les signaux sont associés à de nombreuses structures telles que task_struct, signal_struct et sighand_struct, comme suit:
Je pense qu'il est très facile de se confondre, j'ai donc résumé les parties que je pense personnellement importantes pour chaque objectif comme suit.
La génération du signal met le processus qui reçoit le signal en attente de signal (en attente de la livraison du signal) jusqu'à ce qu'il soit traité par la livraison du signal. Cette condition est indiquée par l'indicateur TIF_SIGPENDING et est enregistrée dans les indicateurs de thread (indicateurs).
Le signal reçu est géré par la structure sigqueue et contient des informations associées (* 1). Ils sont placés dans une file d'attente (attachée à la liste des envois). Ce faisant, l'une des deux files d'attente (pour un processus spécifique ou pour l'ensemble du groupe de threads) est utilisée en fonction de la destination du signal.
De plus, le signal maintenu est enregistré sous forme de signal. Vous pouvez trouver cette valeur dans / proc / <PID> / status
(voir" Vérification de l'état du signal "ci-dessus et" Bonus "voir ci-dessous).
--Pour un process spécifique
![all_signal-sig_pending.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283234/aea41c44-8e3c-873e-5ecc-35ed45d56717.jpeg)
--Pour tout le groupe de threads
![all_signal-sig_share_pending.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283234/13ad925f-3ba5-d410-0e47-da8a251b2ea3.jpeg)
(* 1) Certaines informations font référence aux membres de la structure kernel_siginfo suivante.
membre | sens |
---|---|
si_signo | Numéro de signal |
si_code | Code indiquant l'origine du signal(※2) |
si_errno | Code d'erreur de l'instruction à l'origine du signal |
__sifilelds | Si selon la condition à cause de l'union_pid (Pid de destination)、si_uid (Uid de destination)Changer pour |
(* 2) Les valeurs suivantes sont entrées dans le code qui indique la source du signal.
Extrait de include / uapi / asm-generic / siginfo.h
Nom de la définition | valeur | sens |
---|---|---|
SI_USER | 0 | kill()、sigsend()、raise()Transmission du signal par |
SI_KERNEL | 0x80 | Signalisation par le noyau |
SI_QUEUE | -1 | sigqueue()Transmission du signal par |
SI_TIMER | -2 | Transmission du signal dans le temps du temporisateur POSIX |
SI_MESGQ | -3 | Transmission du signal en raison du changement d'état de la file d'attente de messages POSIX |
SI_ASYNCIO | -4 | Asynchrone I/O (AIO)Transmission du signal à la fin |
SI_SIGIO | -5 | Transmission du signal par mise en file d'attente SIGIO |
SI_TKILL | -6 | tkill()、tgkill()Transmission du signal par |
SI_DETHREAD | -7 | sent by execve() killing subsidiary threads [^si_dethread] |
SI_ASYNCNL | -60 | getaddrinfo_a()Transmission du signal en effectuant la recherche de nom dans |
Les valeurs ci-dessus sont communes à tous les signaux. En fonction du signal particulier, une autre valeur peut être entrée comme suit [^ 2 sigaction].
Nom du signal | si_Valeur pour entrer le code |
---|---|
SIGBUG | BUS_* |
SIGCHLD | CLD_* |
SIGFPE | FPE_* |
SIGILL | ILL_* |
SIGPOLL/SIGIO | POLL_* |
SIGSEGV | SEGV_* |
SIGTRAP | TRAP_* |
SIGSYS | SYS_SECCOMP |
[^ signauxet]: Pour le jeu de signaux, reportez-vous à "Vérification de l'état du signal (jeu de signaux)" ci-dessus.
[^ si_dethread]: man 2 execve
J'ai vérifié la source du noyau, etc., mais je n'ai trouvé aucune information au-delà de ce texte original et je n'ai pas bien compris le sens, donc je vais le décrire tel quel.
Les numéros de signaux bloqués par sigprocmask () etc. sont enregistrés dans les informations de bloc (bloqué, readl_blocked [^ real_blocked]). Vous pouvez trouver cette valeur dans / proc / <PID> / status
(voir" Vérification de l'état du signal "ci-dessus et" Bonus "voir ci-dessous).
[^ real_blocked]: real_blocked semble être défini lors de l'utilisation de l'appel système rt_sigtimedwait (), rt_sigtimedwait_time32 (), rt_sigtimedwait_time64 ().
Si vous modifiez les paramètres d'action du signal avec sigaction () etc., certaines informations (*) seront stockées dans le descripteur du gestionnaire de signaux (sighand_struct). De plus, l'action du descripteur du gestionnaire de signaux [x] est le numéro du signal (_NSIG) ) Puisqu'il s'agit d'un tableau (action [numéro de signal-1]), il est défini pour chaque signal.
(*) Certaines informations se réfèrent aux membres de la structure de sigaction suivante.
membre | sens |
---|---|
sa_flags | SA montrant comment utiliser les signaux_*drapeau[^2sigaction] |
sa_handler | SIG_IGN、SIG_Types d'actions de signal tels que DFL, pointeurs vers les gestionnaires de signaux |
sa_mask | Bloquer lors de l'exécution du gestionnaire(Interdire la réception)signal |
Jetons un coup d'œil au flux entre l'envoi d'un signal et le traitement de l'action du signal (traitement lorsque kill -TERM 1234
est exécuté).
A partir de maintenant, nous examinerons chacune des "génération de signal" et "livraison de signal" séparément (la partie "..." est omise).
La tâche principale à ce stade est d'enregistrer dans la structure de données du processus de destination qu '"un signal a été envoyé (en attente de livraison du signal)" dans la structure de données du processus de destination et de le notifier (comme un fonctionnement standard). Les actions de signal sont gérées par la livraison de signal).
En résumé de la lecture de code qui suit, l'image globale du traitement dans la génération de signal est illustrée. Puisqu'il y a beaucoup d'appels de fonction, dans la lecture de code, nous commencerons par __send_signal (), qui est la fonction la plus importante (fonction principale).
__send_signal()
Cette fonction décide de délivrer le signal transmis (prepare_signal ()) et place le signal dans la file d'attente (__sigqueue_alloc (), list_add_tail ()), qui enregistre le numéro du signal d'attente ( sigaddset ()). Pour la livraison, définissez TIF_SIGPENDING dans l'indicateur de thread du descripteur de processus pour réveiller le processus livrable (complete_signal ()).
Voir ci-dessous pour plus de détails.
1065 static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
1066 enum pid_type type, bool force)
1067 {
1068 struct sigpending *pending;
1069 struct sigqueue *q;
1070 int override_rlimit;
1071 int ret = 0, result;
....
1076 if (!prepare_signal(sig, t, force))
1077 goto ret;
La ligne 1076 prepare_signal () vérifie si le signal doit être délivré Faire. Par exemple, si le gestionnaire de signal (t-> sighand-> action [sig -1] .sa.sa_handler) a SIG_IGN défini, ou si le comportement standard est un signal ignore (Ign), il n'est pas nécessaire de fournir Alors sautez à l'étiquette ret
. Si le signal est bloqué, vous devez maintenir le signal, ainsi que le reste.
Il effectue également les opérations suivantes pour le signal d'arrêt et SIGCONT:
Supprimez SIGCONT de la file d'attente de maintien du signal (*).
Supprimez tous les signaux d'arrêt de la file d'attente de maintien du signal (*) et utilisez wake_up_state () pour réveiller le thread dans l'état __TASK_STOPPED (redémarrer le processus).
Si le processus (groupe de threads) est en train de se terminer (SIGNAL_STOP_STOPPED), on considère que le processus est terminé et SIGNAL_CLD_CONTINUED et SIGNAL_STOP_CONTINUED sont positionnés dans les drapeaux du descripteur de processus. Si group_stop_count est compté même s'il n'est pas en cours de terminaison, cela signifie que le processus de terminaison est déjà terminé (probablement), alors définissez SIGNAL_CLD_STOPPED et SIGNAL_STOP_CONTINUED dans les drapeaux du descripteur de processus.
(*) Comme expliqué dans "Structure des données", il existe deux types de files d'attente de signal: "pour un processus spécifique (t-> en attente)" et "pour tout le groupe de threads (t-> signal-> shared_pending)". il y a. Ici, nous supprimons le signal pertinent des deux files d'attente.
1078
1079 pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
La ligne 1079 détermine s'il faut utiliser la file d'attente de maintien du signal «pour un processus spécifique» ou «pour l'ensemble du groupe de threads» pour en attente. Cette fois, la commande kill passe PIDTYPE_TGID au type, donc l'attente concerne tout le groupe de threads.
1080 /*
1081 * Short-circuit ignored signals and support queuing
1082 * exactly one non-rt signal, so that we can get more
1083 * detailed information about the cause of the signal.
1084 */
1085 result = TRACE_SIGNAL_ALREADY_PENDING;
1086 if (legacy_queue(pending, sig))
1087 goto ret;
À la ligne 1086 legacy_queue (), sig
est" signal standard "et" déjà signal S'il se trouve dans la file d'attente d'attente, il saute à l'étiquette ret
(un signal standard peut recevoir plus d'un signal identique, mais un seul).
1088
1089 result = TRACE_SIGNAL_DELIVERED;
1090 /*
1091 * Skip useless siginfo allocation for SIGKILL and kernel threads.
1092 */
1093 if ((sig == SIGKILL) || (t->flags & PF_KTHREAD))
1094 goto out_set;
Comme commenté, si le signal est SIGKILL ** OU ** la destination est un thread du noyau (indicateur PF_KTHREAD), il saute à l'étiquette ʻout_set` et n'effectue pas le traitement ultérieur d'enregistrement de file d'attente de maintien du signal.
1096 /*
1097 * Real-time signals must be queued if sent by sigqueue, or
1098 * some other real-time mechanism. It is implementation
1099 * defined whether kill() does so. We attempt to do so, on
1100 * the principle of least surprise, but since kill is not
1101 * allowed to fail with EAGAIN when low on memory we just
1102 * make sure at least one signal gets delivered and don't
1103 * pass on the info struct.
1104 */
1105 if (sig < SIGRTMIN)
1106 override_rlimit = (is_si_special(info) || info->si_code >= 0);
1107 else
1108 override_rlimit = 0;
1109
Puisque SIGRTMIN sur la ligne 1105 est 32, le processus se branche selon que "sig" est un signal standard ou non.
Dans la vraie racine (ligne 1106) is_si_special (), ʻinfo est SEND_SIG_NOINFO (en mode utilisateur). Renvoie true si le processus a envoyé un signal) ou SEND_SIGPRIV (un signal a été envoyé depuis le noyau). Il renvoie également true si ʻinfo-> si_code> = 0
(SI_USER ou SI_KERNEL) sur la droite.
Notez que cette fois, SI_USER est défini dans ʻinfo-> si_code en exécutant la commande kill, donc ʻoverride_rlimit
est mis à true.
1110 q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
1111 if (q) {
1112 list_add_tail(&q->list, &pending->list);
1113 switch ((unsigned long) info) {
1114 case (unsigned long) SEND_SIG_NOINFO:
1115 clear_siginfo(&q->info);
1116 q->info.si_signo = sig;
1117 q->info.si_errno = 0;
1118 q->info.si_code = SI_USER;
1119 q->info.si_pid = task_tgid_nr_ns(current,
1120 task_active_pid_ns(t));
1121 rcu_read_lock();
1122 q->info.si_uid =
1123 from_kuid_munged(task_cred_xxx(t, user_ns),
1124 current_uid());
1125 rcu_read_unlock();
1126 break;
1127 case (unsigned long) SEND_SIG_PRIV:
1128 clear_siginfo(&q->info);
1129 q->info.si_signo = sig;
1130 q->info.si_errno = 0;
1131 q->info.si_code = SI_KERNEL;
1132 q->info.si_pid = 0;
1133 q->info.si_uid = 0;
1134 break;
1135 default:
1136 copy_siginfo(&q->info, info);
1137 break;
1138 }
....
À la ligne 1110, __sigqueue_alloc () a un nouveau type de sigqueue q
pour la gestion du signal. Tentatives d'allocation. Le traitement interne de cette fonction vérifie si le nombre de signaux détenus par le propriétaire (utilisateur) du processus de destination dépasse la limite supérieure, et sinon, tente d'allouer. Cependant, si ʻoverride_rlimit` est vrai, il essaiera d'allouer sans vérifier la limite supérieure.
Les lignes 1112 à 1138 sont le traitement lorsque l'allocation est réussie.
À la ligne 1112 list_add_tail (), le «q» est alloué à la file d'attente de maintien du signal. Est enregistré (connecté à la liste), et dans le traitement à partir de la 1113ème ligne, les informations de signal sont stockées dans «q» selon l'argument «info».
1157 out_set:
....
1159 sigaddset(&pending->signal, sig);
....
Ajoutez le numéro de signal actuel au signal défini dans la file d'attente de maintien du signal (voir «État du signal» et «Structure des données» ci-dessus).
1175 complete_signal(sig, t, type);
1176 ret:
1177 trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
1178 return ret;
1179 }
Le complete_signal () sur la ligne 1175 sera décrit plus tard.
La ligne 1177 trace_signal_generate () est un point de trace nommé signal_generate dans la macro TRACE_EVENT. Semble créer. Il est utilisé (signalé) pour afficher des informations de trace du noyau telles que:
kill-5371 [003] 1058202.036613: signal_generate: sig=15 errno=0 code=0 comm=sleep pid 5359 grp=1 res=0
complete_signal()
Cette fonction est appelée lorsque vous décidez de délivrer le signal transmis avec __send_signal ().
Ensuite, utilisez cette fonction pour trouver le processus prêt pour la livraison du signal. S'il est trouvé, définissez TIF_SIGPENDING dans l'indicateur de thread du processus correspondant et réveillez-vous (notifier).
Voir ci-dessous pour plus de détails.
984 static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
985 {
986 struct signal_struct *signal = p->signal;
987 struct task_struct *t;
988
989 /*
990 * Now find a thread we can wake up to take the signal off the queue.
991 *
992 * If the main thread wants the signal, it gets first crack.
993 * Probably the least surprising to the average bear.
994 */
995 if (wants_signal(sig, p))
996 t = p;
...
La ligne 995 want_signal () permet la livraison du signal (prêt) avec le modèle suivant: Recherchez un processus.
--Le signal n'est pas bloqué (SIGKILL, SIGSTOP ne peut pas être bloqué) ** ET **
--Le processus ne se termine pas (p-> flags n'a pas PF_ EXITING) ** AND **
--Signal (sig
) est SIGKILL (force sans regarder l'état du processus ou la présence du drapeau)
--Le signal n'est pas bloqué (SIGKILL, SIGSTOP ne peut pas être bloqué) ** ET ** --Le processus ne se termine pas (p-> flags n'a pas PF_ EXITING) ** AND ** --L'état du processus (p-> état) n'est pas suspendu (__TASK_STOPPED) ou arrêté par un débogueur, etc. (__TASK_TRACED) ** AND **
1021 /*
1022 * Found a killable thread. If the signal will be fatal,
1023 * then start taking the whole group down immediately.
1024 */
1025 if (sig_fatal(p, sig) &&
1026 !(signal->flags & SIGNAL_GROUP_EXIT) &&
1027 !sigismember(&t->real_blocked, sig) &&
1028 (sig == SIGKILL || !p->ptrace)) {
Comme vous pouvez le voir dans les commentaires, recherchez les fils Killable. Si toutes les conditions suivantes sont remplies, le traitement des lignes 1039 à 1042 est effectué.
(*) Sig_fatal () renvoie true dans les deux modèles suivants.
--Le signal est un signal standard ** ET **
--Le signal est un signal en temps réel ** ET **
1029 /*
1030 * This signal will be fatal to the whole group.
1031 */
1032 if (!sig_kernel_coredump(sig)) {
sig_kernel_coredump () retourne true lorsque le comportement standard n'est pas un signal de vidage de mémoire (Core).
1033 /*
1034 * Start a group exit and wake everybody up.
1035 * This way we don't have other threads
1036 * running and doing things after a slower
1037 * thread has the fatal signal pending.
1038 */
1039 signal->flags = SIGNAL_GROUP_EXIT;
1040 signal->group_exit_code = sig;
1041 signal->group_stop_count = 0;
1042 t = p;
1043 do {
1044 task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
1045 sigaddset(&t->pending.signal, SIGKILL);
1046 signal_wake_up(t, 1);
1047 } while_each_thread(p, t);
1048 return;
1049 }
1050 }
Après le traitement des lignes 1039 à 1042, le traitement suivant est mis en boucle jusqu'à ce que le do-while scanne le groupe de threads.
Lorsque la boucle est sortie, le traitement par cette fonction se termine.
1052 /*
1053 * The signal is already in the shared-pending queue.
1054 * Tell the chosen thread to wake up and dequeue it.
1055 */
1056 signal_wake_up(t, sig == SIGKILL);
1057 return;
1058 }
Si vous pouvez atteindre ce point, utilisez l'indicateur de thread (t-) avec signal_wake_up (). Définissez TIF_SIGPENDING dans> flags) pour réveiller le processus dans l'état TASK_INTERRUPTIBLE (notifier le signal de maintien). Il réveille également les processus dans l'état TASK_WAKEKILL si le signal est SIGKILL.
C'est la fin du processus de "génération de signal". Ensuite, regardons "la livraison du signal".
La tâche principale à ce stade est d'effectuer des actions de signal telles que des gestionnaires de signaux et un comportement standard. Cependant, cela n'est possible que si le processus a un signal en attente (avec le drapeau TIF_SIGPENDING décrit dans "Génération de signal" ci-dessus).
En tant que résumé de la lecture de code qui suit, l'image globale du traitement dans la délivrance du signal est illustrée. Notez que certains codes pour la livraison du signal dépendent de l'architecture, mais ici nous allons regarder le code pour x86 (sous arch / x86).
exit_to_usermode_loop()
Cette fonction est appelée à chaque fois que vous revenez du mode noyau au mode utilisateur (gestionnaire d'interruption / d'exception, après le traitement des appels système, etc.) pour vérifier l'indicateur de thread. A ce moment, s'il y a TIF_SIGPENDING, do_signal () est appelé (ligne 160).
Voir ci-dessous pour plus de détails.
arch/x86/entry/common.c (L136)
136 static void exit_to_usermode_loop(struct pt_regs *regs, u32 cached_flags)
137 {
138 /*
139 * In order to return to user mode, we need to have IRQs off with
140 * none of EXIT_TO_USERMODE_LOOP_FLAGS set. Several of these flags
141 * can be set at any time on preemptible kernels if we have IRQs on,
142 * so we need to loop. Disabling preemption wouldn't help: doing the
143 * work to clear some of the flags can sleep.
144 */
145 while (true) {
146 /* We have work to do. */
147 local_irq_enable();
...
158 /* deal with pending signal delivery */
159 if (cached_flags & _TIF_SIGPENDING)
160 do_signal(regs);
...
171 /* Disable IRQs and retry */
172 local_irq_disable();
173
174 cached_flags = READ_ONCE(current_thread_info()->flags);
175
176 if (!(cached_flags & EXIT_TO_USERMODE_LOOP_FLAGS))
177 break;
178 }
179 }
do_signal()
Cette fonction effectue un comportement standard (get_signal ()) et l'exécution du gestionnaire de signal (handle_signal ()). De plus, si nécessaire, l'appel système est réexécuté et les informations de bloc de signal temporairement réécrites par un appel système spécifique sont restaurées.
Voir ci-dessous pour plus de détails.
arch/x86/kernel/signal.c (L806)
806 /*
807 * Note that 'init' is a special process: it doesn't get signals it doesn't
808 * want to handle. Thus you cannot kill init even with a SIGKILL even by
809 * mistake.
810 */
811 void do_signal(struct pt_regs *regs)
812 {
813 struct ksignal ksig;
814
815 if (get_signal(&ksig)) {
816 /* Whee! Actually deliver the signal. */
817 handle_signal(&ksig, regs);
818 return;
819 }
820
Get_signal () sur la ligne 815 effectue une opération standard et récupère le signal de la file d'attente. Si le résultat est vrai (s'il est récupéré), exécutez handle_signal () pour exécuter le gestionnaire de signaux. S'il est faux (n'a pas pu être récupéré), le traitement suivant est effectué (get_signal () et handle_signal () sont des fonctions importantes, les détails seront donc décrits plus loin).
821 /* Did we come from a system call? */
822 if (syscall_get_nr(current, regs) >= 0) {
823 /* Restart the system call - no handlers present */
824 switch (syscall_get_error(current, regs)) {
825 case -ERESTARTNOHAND:
826 case -ERESTARTSYS:
827 case -ERESTARTNOINTR:
828 regs->ax = regs->orig_ax;
829 regs->ip -= 2;
830 break;
831
832 case -ERESTART_RESTARTBLOCK:
833 regs->ax = get_nr_restart_syscall(regs);
834 regs->ip -= 2;
835 break;
836 }
837 }
Il s'agit du processus lié à la réexécution de l'appel système (c'est parce que si un signal est reçu pendant que l'appel système est en cours de traitement, le processus peut être interrompu avec une erreur).
La ligne 822 syscall_get_nr () est le registre du mode utilisateur ( Obtenez le numéro d'appel du système auprès des regs
). S'il est supérieur ou égal à 0, alors ligne 824 syscall_get_erro () Il obtient le numéro d'erreur et le noyau réexécute l'appel système en fonction du numéro d'erreur.
La ré-exécution des appels système a été décrite en détail dans l'article "Ré-exécution des appels système Linux". Cliquez ici pour des détails tels que la signification de regs-> ip- = 2
.
838
839 /*
840 * If there's no signal to deliver, we just put the saved sigmask
841 * back.
842 */
843 restore_saved_sigmask();
844 }
Dans restore_saved_sigmask (), par des appels système tels que ppoll, pselect, epoll, sigsuspend Restaure les informations de bloc de signal temporairement réécrites (task_struct-> bloqué) à partir des informations de bloc de signal précédemment enregistrées (task_struct-> saved_sigmask).
get_signal()
Cette fonction récupère un signal de la file d'attente de maintien du signal et effectue des actions standard en réponse au signal.
Voir ci-dessous pour plus de détails.
2521 bool get_signal(struct ksignal *ksig)
2522 {
2523 struct sighand_struct *sighand = current->sighand;
2524 struct signal_struct *signal = current->signal;
2525 int signr;
....
2540 relock:
....
2542 /*
2543 * Every stopped thread goes here after wakeup. Check to see if
2544 * we should notify the parent, prepare_signal(SIGCONT) encodes
2545 * the CLD_ si_code into SIGNAL_CLD_MASK bits.
2546 */
2547 if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
2548 int why;
2549
2550 if (signal->flags & SIGNAL_CLD_CONTINUED)
2551 why = CLD_CONTINUED;
2552 else
2553 why = CLD_STOPPED;
2554
2555 signal->flags &= ~SIGNAL_CLD_MASK;
2556
2557 spin_unlock_irq(&sighand->siglock);
2558
2559 /*
2560 * Notify the parent that we're continuing. This event is
2561 * always per-process and doesn't make whole lot of sense
2562 * for ptracers, who shouldn't consume the state via
2563 * wait(2) either, but, for backward compatibility, notify
2564 * the ptracer of the group leader too unless it's gonna be
2565 * a duplicate.
2566 */
2567 read_lock(&tasklist_lock);
2568 do_notify_parent_cldstop(current, false, why);
2569
2570 if (ptrace_reparented(current->group_leader))
2571 do_notify_parent_cldstop(current->group_leader,
2572 true, why);
2573 read_unlock(&tasklist_lock);
2574
2575 goto relock;
2576 }
....
Comme mentionné dans le commentaire de la ligne 2543, le drapeau de la ligne 2547 (SIGNAL_CLD_MASK == SIGNAL_CLD_STOPPED ou SIGNAL_CLD_CONTINUED) est utilisé lors du traitement de SIGCONT avec prepare_signal () appelé par la génération de signal (__send_signal ()). Sera réglé. Si cet indicateur est défini, do_notify_parent_cldstop () envoie un signal (SIGCHLD) au parent.
2588 for (;;) {
2589 struct k_sigaction *ka;
....
2616 /*
2617 * Signals generated by the execution of an instruction
2618 * need to be delivered before any other pending signals
2619 * so that the instruction pointer in the signal stack
2620 * frame points to the faulting instruction.
2621 */
2622 signr = dequeue_synchronous_signal(&ksig->info);
2623 if (!signr)
2624 signr = dequeue_signal(current, ¤t->blocked, &ksig->info);
2625
2626 if (!signr)
2627 break; /* will return 0 */
....
Ligne 2622dequeue_synchronous_signal()Signalsynchroniséavec(SIG[SEGV|BUS|ILL|TRAP|FPE|SYS])Delafiled'attente.Sinon,ligne2624dequeue_signal()Supprime le signal asynchrone de la file d'attente avec. Sinon, sortez de la boucle et terminez le traitement avec cette fonction.
2635 ka = &sighand->action[signr-1];
2636
2637 /* Trace actually delivered signals. */
2638 trace_signal_deliver(signr, &ksig->info, ka);
La ligne 2638 trace_signal_deliver () est une macro TRACE_EVENT nommée signal_deliver. Il semble créer un point de trace. Il est utilisé (signalé) pour afficher des informations de trace du noyau telles que: En regardant le commentaire original, il semble que le signal a été délivré lorsque nous sommes arrivés ici.
trace-cmd-17636 [001] 607498.455844: signal_deliver: sig=2 errno=0 code=128 sa_handler=5567b90d1540 sa_flags=1400000
Ce qui suit est le traitement lié aux actions de signal comme l'ignorance de signal, le gestionnaire de signal et le fonctionnement standard. Branches selon le contenu de «ka» (ligne 2635).
2639
2640 if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
2641 continue;
Si l'action du signal est définie sur ignore (SIG_IGN) dans l'API sigaction () du langage C, etc., rien n'est fait.
2642 if (ka->sa.sa_handler != SIG_DFL) {
2643 /* Run the handler. */
2644 ksig->ka = *ka;
2645
2646 if (ka->sa.sa_flags & SA_ONESHOT)
2647 ka->sa.sa_handler = SIG_DFL;
2648
2649 break; /* will return non-zero "signr" value */
2650 }
La ligne 2642 met l'adresse de «ka» dans «ksig-> ka» sur la ligne 2644 si ce n'est pas SIG_DFL (c'est-à-dire si vous avez défini un gestionnaire de signaux). Ceci est utilisé lors de la définition du registre pour le traitement de l'utilisateur avec handle_signal () après avoir quitté get_signal ().
SA_ONESHOT (un drapeau qui peut être défini avec sigaction ()) sur la ligne 2646 signifie que l'action de signal est retournée à la valeur par défaut (SIG_DFL) après l'exécution du gestionnaire de signal. Autrement dit, le gestionnaire de signaux ne s'exécute que la première fois qu'un signal est reçu.
Enfin, break rompt la boucle et termine le processus avec cette fonction. Dans ce cas, signr
doit contenir le numéro de signal récupéré, ainsi handle_signal () décrit ci-dessous est exécuté.
2651
2652 /*
2653 * Now we are doing the default action for this signal.
2654 */
2655 if (sig_kernel_ignore(signr)) /* Default is nothing. */
2656 continue;
....
De là, c'est le processus lié à l'exécution de l'opération standard.
La ligne 2655 sig_kernel_ignore () ignore le comportement standard de signr
(Ign) Renvoie vrai. Dans ce cas, ne faites rien.
2671
2672 if (sig_kernel_stop(signr)) {
2673 /*
2674 * The default action is to stop all threads in
2675 * the thread group. The job control signals
2676 * do nothing in an orphaned pgrp, but SIGSTOP
2677 * always works. Note that siglock needs to be
2678 * dropped during the call to is_orphaned_pgrp()
2679 * because of lock ordering with tasklist_lock.
2680 * This allows an intervening SIGCONT to be posted.
2681 * We need to check for that and bail out if necessary.
2682 */
2683 if (signr != SIGSTOP) {
2684 spin_unlock_irq(&sighand->siglock);
2685
2686 /* signals can be posted during this window */
2687
2688 if (is_current_pgrp_orphaned())
2689 goto relock;
2690
2691 spin_lock_irq(&sighand->siglock);
2692 }
2693
2694 if (likely(do_signal_stop(ksig->info.si_signo))) {
2695 /* It released the siglock. */
2696 goto relock;
2697 }
2698
2699 /*
2700 * We didn't actually stop, due to a race
2701 * with SIGCONT or something like that.
2702 */
2703 continue;
2704 }
À la ligne 2672 sig_kernel_stop (), l'opération standard de signr
est une pause de processus ( Renvoie vrai si le signal est Stop). Si vrai, définissez SIGNAL_STOP_STOPPED dans les indicateurs de descripteur de processus (indicateurs) à la ligne 2694 à do_signal_stop (). Levez-vous et suspendez le processus (TASK_STOPPED).
Cependant, si le signal est autre que SIGSTOP et que le groupe de processus est orphelin (sans parent), do_signal_stop () n'est pas exécuté (ne s'arrête pas).
2705
2706 fatal:
2707 spin_unlock_irq(&sighand->siglock);
....
2711 /*
2712 * Anything else is fatal, maybe with a core dump.
2713 */
2714 current->flags |= PF_SIGNALED;
2715
2716 if (sig_kernel_coredump(signr)) {
2717 if (print_fatal_signals)
2718 print_fatal_signal(ksig->info.si_signo);
2719 proc_coredump_connector(current);
2720 /*
2721 * If it was able to dump core, this kills all
2722 * other threads in the group and synchronizes with
2723 * their demise. If we lost the race with another
2724 * thread getting here, it set group_exit_code
2725 * first and our do_group_exit call below will use
2726 * that value and ignore the one we pass it.
2727 */
2728 do_coredump(&ksig->info);
2729 }
....
À la ligne 2716 sig_kernel_coredump (), le comportement standard de signr
est le vidage de mémoire (Core). Renvoie vrai. Si vrai, définissez SIGNAL_GROUP_COREDUMP dans les indicateurs de descripteur de signal (indicateurs) à la ligne 2728 do_coredump (). , Effectue le traitement de génération de vidage de mémoire.
2731 /*
2732 * Death signals, no core dump.
2733 */
2734 do_group_exit(ksig->info.si_signo);
2735 /* NOTREACHED */
2736 }
Si le comportement standard de signr
est l'arrêt du processus (Term) ou le vidage de mémoire (Core), ligne 2724 [do_group_exit ()](https://github.com/torvalds/linux/blob/v5.5/kernel/ Définissez SIGNAL_GROUP_EXIT dans les indicateurs de descripteur de signal (indicateurs) dans exit.c # L876) pour terminer le processus (groupe de threads).
Notez que «}» à la ligne 2736 est une paire de «for (;;) {» à la ligne 2588. Vous pouvez interrompre cette boucle avec une pause sur la ligne 2627 ou une pause sur la ligne 2649. Le premier est lorsqu'il n'y a rien à récupérer de la file d'attente de maintien de signal, et le second est lorsque le gestionnaire de signal est défini dans l'action de signal (sa_handler).
2737 spin_unlock_irq(&sighand->siglock);
2738
2739 ksig->sig = signr;
2740 return ksig->sig > 0;
2741 }
Détermine si le dernier numéro de signal extrait de la file d'attente est supérieur à 0. S'il est grand, cela signifie que le signal a été récupéré et retourne true, et passe au prochain handle_signal ().
handle_signal()
Cette fonction définit la trame de pile en mode utilisateur pour la réexécution des appels système (si nécessaire) et l'exécution du gestionnaire de signaux.
arch/x86/kernel/signal.c (L71)
710 static void
711 handle_signal(struct ksignal *ksig, struct pt_regs *regs)
712 {
713 bool stepping, failed;
714 struct fpu *fpu = ¤t->thread.fpu;
715
...
719 /* Are we from a system call? */
720 if (syscall_get_nr(current, regs) >= 0) {
721 /* If so, check system call restarting.. */
722 switch (syscall_get_error(current, regs)) {
723 case -ERESTART_RESTARTBLOCK:
724 case -ERESTARTNOHAND:
725 regs->ax = -EINTR;
726 break;
727
728 case -ERESTARTSYS:
729 if (!(ksig->ka.sa.sa_flags & SA_RESTART)) {
730 regs->ax = -EINTR;
731 break;
732 }
733 /* fallthrough */
734 case -ERESTARTNOINTR:
735 regs->ax = regs->orig_ax;
736 regs->ip -= 2;
737 break;
738 }
739 }
740
...
Bien qu'il ait également été émis par do_signal () [^ restart_syscall], il existe également un traitement lié à la réexécution de l'appel système. Cependant, il n'y a que deux modèles dans lesquels le noyau réexécute les appels système:
--Si l'erreur est -ERESTARTSYS (ligne 728) et que sa_flags a SA_RESTART (un drapeau qui peut être défini avec sigaction ()). --Si l'erreur est -ERESTARTNOINTR (ligne 734) à la ligne 734
Sinon, le noyau ne réexécutera pas l'appel système et définira regs-> ax
sur -EINTR (une erreur indiquant que l'appel de fonction a été interrompu). A partir de cette erreur, l'utilisateur (condition du programme utilisateur) décide de réexécuter l'appel système.
[^ restart_syscall]: La réexécution de l'appel système avec do_signal () est le chemin (lorsque get_signal () est faux) qui est pris lorsque handle_signal () n'est pas exécuté, donc il ne chevauche pas le processus de réexécution de l'appel système ici. ..
750 failed = (setup_rt_frame(ksig, regs) < 0);
...
751 if (!failed) {
752 /*
753 * Clear the direction flag as per the ABI for function entry.
754 *
755 * Clear RF when entering the signal handler, because
756 * it might disable possible debug exception from the
757 * signal handler.
758 *
759 * Clear TF for the case when it wasn't set by debugger to
760 * avoid the recursive send_sigtrap() in SIGTRAP handler.
761 */
762 regs->flags &= ~(X86_EFLAGS_DF|X86_EFLAGS_RF|X86_EFLAGS_TF);
763 /*
764 * Ensure the signal handler starts with the new fpu state.
765 */
766 fpu__clear(fpu);
767 }
768 signal_setup_done(failed, ksig, stepping);
769 }
En ligne 750 setup_rt_frame (), en mode utilisateur pour l'exécution du gestionnaire de signaux Définissez le cadre de pile de. La trame de pile contient les informations requises pour exécuter le gestionnaire de signaux (numéro de signal, informations de structure siginfo et informations pour appeler l'appel système rt_sigreturn () à partir du processus en mode utilisateur. Rt_sigreturn () est après l'exécution du gestionnaire de signaux. Requis pour revenir en mode noyau.
De plus, si setup_rt_frame () est défini avec succès, les drapeaux (regs-> flags
) et l'état du FPU (registre à virgule flottante) sont effacés sur les lignes 762 et 766 (pour des raisons inconnues).
Et dans le dernier (ligne 768) signal_setup_done (), le paramètre succès ou échec dans setup_rt_frame () Le processus se ramifie.
En cas de succès: sigorsets () avec les informations sur le bloc de processus actuel (actuel-> bloqué) et sigaction () avec l'utilisateur Prend la somme logique (OR) des informations de bloc (sigaction-> sa_mask) définie par et la définit sur les informations de bloc du processus en cours. De plus, si l'indicateur de thread est défini sur TIF_SINGLESTEP (utilisé par le débogueur), alors [ptrace_notify (SIGTRAP)](https://github.com/torvalds/linux/blob/v5.5/include/linux/tracehook. Exécutez h # L146).
En cas d'échec: Envoyez SIGSEGV au processus en cours avec force_sigsegv ().
C'est la fin du processus de "livraison du signal".
/ proc / <PID> / status
J'ai cherché dans le code pour savoir d'où provenaient les valeurs telles que SigPnd, ShdPnd, etc. dans / proc / <PID> / status
.
Il y avait une réponse dans task_sig (). Par exemple, SigBlk («bloqué») contient «p-> bloqué» à la ligne 283.
266 static inline void task_sig(struct seq_file *m, struct task_struct *p)
267 {
268 unsigned long flags;
269 sigset_t pending, shpending, blocked, ignored, caught;
270 int num_threads = 0;
271 unsigned int qsize = 0;
272 unsigned long qlim = 0;
273
274 sigemptyset(&pending);
275 sigemptyset(&shpending);
276 sigemptyset(&blocked);
277 sigemptyset(&ignored);
278 sigemptyset(&caught);
279
280 if (lock_task_sighand(p, &flags)) {
281 pending = p->pending.signal;
282 shpending = p->signal->shared_pending.signal;
283 blocked = p->blocked;
284 collect_sigign_sigcatch(p, &ignored, &caught);
285 num_threads = get_nr_threads(p);
286 rcu_read_lock(); /* FIXME: is this correct? */
287 qsize = atomic_read(&__task_cred(p)->user->sigpending);
288 rcu_read_unlock();
289 qlim = task_rlimit(p, RLIMIT_SIGPENDING);
290 unlock_task_sighand(p, &flags);
291 }
292
293 seq_put_decimal_ull(m, "Threads:\t", num_threads);
294 seq_put_decimal_ull(m, "\nSigQ:\t", qsize);
295 seq_put_decimal_ull(m, "/", qlim);
296
297 /* render them all */
298 render_sigset_t(m, "\nSigPnd:\t", &pending);
299 render_sigset_t(m, "ShdPnd:\t", &shpending);
300 render_sigset_t(m, "SigBlk:\t", &blocked);
301 render_sigset_t(m, "SigIgn:\t", &ignored);
302 render_sigset_t(m, "SigCgt:\t", &caught);
303 }
J'ai cherché dans le code la raison pour laquelle SIGKILL et SIGSTOP ne peuvent pas être bloqués.
La réponse était sys_rt_sigprocmask (), qui est appelée à l'exécution sigprocmask () utilisée pour le blocage du signal. SIGKILL et SIGSTOP ont été supprimés des informations de bloc nouvellement définies (new_set
) dans sigdelsetmask () à la ligne 3025 (c'est-à-dire que le réglage pour bloquer SIGKILL et SIGSTOP ne le définit pas réellement).
3003 /**
3004 * sys_rt_sigprocmask - change the list of currently blocked signals
3005 * @how: whether to add, remove, or set signals
3006 * @nset: stores pending signals
3007 * @oset: previous value of signal mask if non-null
3008 * @sigsetsize: size of sigset_t type
3009 */
3010 SYSCALL_DEFINE4(rt_sigprocmask, int, how, sigset_t __user *, nset,
3011 sigset_t __user *, oset, size_t, sigsetsize)
3012 {
3013 sigset_t old_set, new_set;
3014 int error;
....
3022 if (nset) {
3023 if (copy_from_user(&new_set, nset, sizeof(sigset_t)))
3024 return -EFAULT;
3025 sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));
3026
3027 error = sigprocmask(how, &new_set, NULL);
3028 if (error)
3029 return error;
3030 }
....
3037 return 0;
3038 }
Vous pouvez également définir le bloc de signal avec sa_mask dans sigaction (), alors jetons un œil ici également.
Dans le cas de sigaction (), le même traitement a été effectué sur les lignes 3967 et 3968 de sys_rt_sigaction () -> do_sigaction ().
3949 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
3950 {
3951 struct task_struct *p = current, *t;
3952 struct k_sigaction *k;
3953 sigset_t mask;
....
3966 if (act) {
3967 sigdelsetmask(&act->sa.sa_mask,
3968 sigmask(SIGKILL) | sigmask(SIGSTOP));
....
3988 }
....
3991 return 0;
3992 }
J'ai également étudié la raison pour laquelle SIGKILL et SIGSTOP ne peuvent pas être ignorés ou modifiés à partir du code.
Il y avait une réponse à la ligne 3955 de sys_rt_sigaction () -> do_sigaction () qui est appelée lorsque sigaction () est exécuté. Dans la condition la plus à droite, ʻact` est une valeur valide et sig_kernel_only () est vrai. Renvoi d'une erreur (argument invalide) avec -EINVAL activé (si le signal est SIGKILL ou SIGSTOP). Autrement dit, passer SIGKILL et SIGSTOP à sigaction () échouera avec -EINVAL. Dans ce cas, bien sûr, vous ne pouvez pas définir ignore (SIG_IGN) ou signal handler (fonction définie par l'utilisateur).
3949 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
3950 {
3951 struct task_struct *p = current, *t;
3952 struct k_sigaction *k;
3953 sigset_t mask;
3954
3955 if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
3956 return -EINVAL;
....
3966 if (act) {
....
3969 *k = *act;
3970 /*
3971 * POSIX 3.3.1.3:
3972 * "Setting a signal action to SIG_IGN for a signal that is
3973 * pending shall cause the pending signal to be discarded,
3974 * whether or not it is blocked."
3975 *
3976 * "Setting a signal action to SIG_DFL for a signal that is
3977 * pending and whose default action is to ignore the signal
3978 * (for example, SIGCHLD), shall cause the pending signal to
3979 * be discarded, whether or not it is blocked"
3980 */
3981 if (sig_handler_ignored(sig_handler(p, sig), sig)) {
3982 sigemptyset(&mask);
3983 sigaddset(&mask, sig);
3984 flush_sigqueue_mask(&mask, &p->signal->shared_pending);
3985 for_each_thread(p, t)
3986 flush_sigqueue_mask(&mask, &t->pending);
3987 }
3988 }
....
3991 return 0;
3992 }
man 1 bash
man 2 setrlimit
man 2 seccomp
man 2 signal
man 2 sigaction
man 3 getaddrinfo_a
man 5 proc
man 7 signal
man 7 pipe
Recommended Posts