Bases et mécanismes du signal Linux (à partir du noyau v5.5)

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)".

■ Basique

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.

1. Qu'est-ce qu'un signal?

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.

all_signal (2)-flow.jpg

2. Exemple d'utilisation du signal

Pour faciliter l'imagination, voici trois exemples d'utilisation du signal du point de vue de l'utilisateur.

commande kill

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

Touche d'interruption

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

Programme (API en langage 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

3. Numéro et nom du signal

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.

4. Signal standard, signal en temps réel

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].

5. Action de signal

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. ** **

all_signal-sigaction.jpg

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)

Fonctionnement standard

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).

all_signal-kernel_handler.jpg

[^ 5 core]: Voir man 5 core pour plus de détails.

Signal standard et comportement standard correspondant

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.

Exemple de modification d'action de signal

Pour modifier les actions de signal autres que ** SIGKILL ** et ** SIGSTOP **, utilisez l'API (langage C) comme la commande trap ou sigaction ().

commande trap

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 (API en langage C)

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.

6. Bloc de signal

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].

all_signal-sigblock.jpg

[^ 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.

Exemple de réglage de bloc de 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

7. Vérifiez l'état du signal

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

■ Noyau (v5.5)

À 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.

1. Structure des données

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.

Drapeau indiquant un signal en attente

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).

all_signal-thread_flag.jpg

File d'attente pour le maintien du signal (pour des processus spécifiques, pour des groupes de threads entiers)

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.

Informations sur le bloc de signal

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).

all_signal-sig_block.jpg

[^ 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 ().

Informations relatives aux actions de signalisation

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.

all_signal-sig_handle.jpg

(*) 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

2. Mise en œuvre

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).

Génération de signaux

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).

all_signal-signal generate.jpg

__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.

kernel/signal.c (L1065)

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.

kernel/signal.c (L984)

 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".

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).

all_signal-signal delivery.jpg

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.

kernel/signal.c (L2521)

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, &current->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 = &current->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.

C'est la fin du processus de "livraison du signal".

3. Bonus

Code lié au signal dans / 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.

fs/proc/array.c#L266 (L266)

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 }

Pourquoi SIGKILL et SIGSTOP ne peuvent pas bloquer

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).

kernel/signal.c (L3004)

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 }

Ignorez SIGKILL, SIGSTOP ou pourquoi vous ne pouvez pas définir un gestionnaire de signal

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 }

■ Références

Recommended Posts

Bases et mécanismes du signal Linux (à partir du noyau v5.5)
[À voir pour les débutants] Bases de Linux
[Linux] Découvrez les bases des commandes shell
Bases de Linux
Bases de Linux
[Python] Chapitre 02-01 Bases des programmes Python (opérations et variables)
Confirmation des bases du concours professionnel ~ pgcd et lcm ~