This is an old revision of the document!


TP 5 - Signaux

Aides

Liens vers des sections utiles

Les signaux dans Linux

Dans le monde réel, un processus peut découvrir une variété de contingences qui affectent le flux normal de l'exécution. Si le processus ne peut pas gérer, ils sont passés plus loin, le système d'exploitation. Quel système d'exploitation ne sait pas si le processus peut continuer à exécuter normalement sans effets secondaires indésirables est nécessaire pour terminer le processus de force. Une solution à ce problème est les signaux.

Un signal est un logiciel d'interruption du flux normal de l'exécution d'un processus.

Les signaux sont un concept spécifique des systèmes d'exploitation UNIX. Le système d'exploitation utilise pour signer la survenance de circonstances exceptionnelles processus donnant la possibilité de répondre. Chaque signal est associé à une classe d'événements qui peuvent se produire qui répondent à certains critères. Les processus peuvent traiter, bloquer, ignorer ou laisser le système d'exploitation pour exécuter l'action par défaut lors de la réception d'un signal:

  • En règle générale l'action par défaut est processus d'achèvement.
  • Si un processus veut ignorer un signal, le système d'exploitation ne sera pas envoyer ce processus de signal.
  • Si un processus spécifique qui veut signal de bloc, le système d'exploitation ne sera plus envoyer des signaux à traiter ce type en question, mais enregistrera seulement le premier signal de ce type, le reste est perdu. Lorsque le processus décide qu'il veut recevoir des signaux à nouveau de ce type, en cas de signal en attente, il sera envoyé.

L'ensemble de ces signaux est terminé; OS conserve pour chaque processus, une table des actions choisie pour chaque type de signal. A chaque fois que ces actions sont bien définies. Lors du démarrage de la table de processus est initialisé avec l'action par défaut. Le signal de traitement est déterminé par le processus de réception du signal, mais est choisi automatiquement de la table. Les signaux sont flux d'exécution synchrone / asynchrone du processus qui reçoit le signal si l'événement provoquant le signal d'envoi est exécution synchrone / asynchrone des flux de processus.

  • Un événement est flux synchrone de l'exécution du processus qui se produit chaque fois que le programme en cours d'exécution au même point dans le flot d'exécution. Des exemples tentent d'accéder à un emplacement mémoire invalide ou permis, division par zéro, etc.
  • Un événement est asynchrone si il est pas synchronisé. Des exemples d'événements asynchrones: un signal envoyé par un autre processus (terminaison de signal d'un processus enfant), ou une demande de terminaison externe (utilisateur veut réinitialiser l'ordinateur).

Un signal reçu par un processus peut être généré:

  • Soit directement système d'exploitation - où il signale diverses erreurs;
  • ou un processus - qui peut également envoyer ses propres signaux (le signal passera également par le système d'exploitation).

Si deux signaux sont trop proches car ils pourraient être confondus en un seul. Ainsi, normalement, il n'y a pas de mécanisme pour faire en sorte que envoie le signal qu'il a atteint sa destination.

Dans certains cas, il faut savoir, sûrement, qui a envoyé un signal pour atteindre sa destination et donc, le processus lui répondre (faire une des actions possibles). Le système d'exploitation fournit un autre moyen d'envoyer un signal, qui garantit si le signal a atteint sa destination, si cette action a échoué. Ceci est accompli en créant une pile de signaux d'une certaine capacité (il doit être fini, ne pas provoquer des situations de débordement). Envoi d'un signal, le système d'exploitation vérifie si la pile est pleine. Dans ce cas, l'application échoue, l'autre signal est placé sur la pile et l'opération se termine avec succès.

Le signal terme est utilisé pour indiquer alternativement être une sorte d'objets ou réels signaux de ce type.

Génération de signaux

En général, les événements générant des signaux se répartissent en trois catégories principales:

  • Une “erreur” indique qu'un programme a effectué une opération non autorisée et ne peut pas continuer son exécution. Cependant, tous les types d'erreur ne génèrent pas de signaux (en fait, la plupart ne le font pas). Par exemple, ouvrir un fichier inexistant est une erreur, mais cela ne génère pas de signal. Au lieu de cela, l'appel système ouvert renvoie -1 indiquant que l'appel s'est terminé avec une erreur. Généralement, les erreurs associées à certaines bibliothèques sont signalées en renvoyant une valeur spéciale. Les erreurs qui génèrent des signaux sont celles qui peuvent apparaître n'importe où dans le programme, pas seulement dans les appels des bibliothèques. Ils incluent la remise à zéro et l'accès invalide à la mémoire.
  • Un “événement externe” est généralement lié aux E / S et à d'autres processus. Exemples: nouvelle entrée de données, expiration du temporisateur, achèvement d'un processus enfant.
  • La demande explicite indique l'utilisation d'un appel système, tel que kill, pour générer un signal.

Les signaux peuvent être synchrones ou asynchrones:

  • Un signal “synchrone” fait référence à une action spécifique du programme et est envoyé (s'il n'est pas bloqué) au cours de cette action. La plupart des erreurs génèrent des signaux synchrones. Les signaux peuvent également être générés de manière synchrone et par des demandes explicites envoyées par un processus lui-même. Sur certains ordinateurs, certains types d’erreurs matérielles (généralement des exceptions en virgule flottante) ne sont pas signalés comme étant complètement synchroniques et ils peuvent obtenir certaines instructions ultérieurement.
  • Les signaux asynchrones sont générés par des événements incontrôlables par le processus qui les reçoit. Ces signaux arrivent à des moments imprévisibles. Les événements externes génèrent des signaux asynchrones, ainsi que des demandes explicites envoyées par d'autres processus.

Un type de signal donné est synchrone ou asynchrone. Par exemple, les signaux d'erreur sont généralement synchrones car les erreurs génèrent des signaux synchrones. Cependant, tout type de signal peut être généré de manière synchrone ou asynchrone avec une demande explicite.

Soumission et recevoir des signaux

Lorsqu'un signal est généré, il entre dans un état en attente. Normalement, il reste dans cet état pendant très peu de temps et est ensuite envoyé au processus de destination. Toutefois, si ce type de signal est actuellement bloqué, il peut rester à l'état indéfini jusqu'à ce que les signaux de ce type soient déverrouillés. Une fois que ce type de signal est déverrouillé, il sera envoyé immédiatement. Lorsque le signal a été reçu, immédiatement ou avec retard, l'action spécifiée pour ce signal est exécutée. Pour certains signaux, tels que «SIGKILL» et «SIGSTOP», l'action est fixée (le processus est terminé), mais pour la plupart des signaux, le programme peut choisir de:

  • ignorer le signal
  • spécifie une fonction gestionnaire
  • accepte l'action par défaut pour ce type de signal.

Le programme spécifie votre choix en utilisant des fonctionnalités telles que signal ou sigaction . Lorsque le gestionnaire est en cours d'exécution, ce type de signal est normalement verrouillé (le déverrouillage sera effectué par une demande explicite du gestionnaire qui gère le signal).

Exemple d'utilisation signal

Exemple d'utilisation signal

Dans le code ci-dessous, nous visons à capturer les signaux “SIGINT” et “SIGUSR1” et à prendre des mesures si nous les recevons. “SIGINT” est reçu à la fois en utilisant la commande “kill -SIGINT <programme>” et en envoyant la frappe “CTRL + c” au programme.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
 
pid_t child1, child2;
int child1_pid;
 
 
void signal_handler(int signum)
{
 
    switch(signum) {
        case SIGINT:
            printf("CTRL+C received in %d Exiting\n", getpid());
            exit(EXIT_SUCCESS);
        case SIGUSR1:
            printf("SIGUSR1 received. Continuing execution\n");
    }
}
 
int main(void)
{
 
    printf("Process %d started\n", getpid());
 
    /* Semnale ca SIGKILL sau SIGSTOP nu pot fi prinse */
    if (signal(SIGKILL, signal_handler) == SIG_ERR)
        printf("\nYou shall not catch SIGKILL\n");
 
    if(signal(SIGINT, signal_handler) == SIG_ERR) {
        printf("Unable to catch SIGINT");
        exit(EXIT_FAILURE);
    }
 
    if(signal(SIGUSR1, signal_handler) == SIG_ERR) {
        printf("Unable to catch SIGUSR1");
        exit(EXIT_FAILURE);
    }
 
 
    printf("Press CTRL+C to stop us\n");
 
    while(1) {
        sleep(1);
    }
 
    return 0;
}

Notez que le signal SIGKILL ne peut pas être traité (kill -9 <program> ou kill -SIGKILL <program>).

Si l'action spécifiée pour un type de signal est de ignorer , alors tout signal de ce type généré pour le processus en question est ignoré. La même chose se produit si le signal est bloqué à ce moment-là. Un signal négligé dans ce mode ne sera jamais reçu, ou si le programme spécifie ensuite une action différente pour ce type de signal, puis le déverrouille. Si un signal est reçu pour lequel aucun type d'action n'a été spécifié, l'action par défaut est exécutée. Chaque type de signal a sa propre action par défaut. Pour la plupart des signaux, l'action par défaut est mettant fin au processus . Pour certains types de signaux, qui sont des événements sans conséquences majeures, l'action par défaut consiste à ne rien faire.

Lorsqu'un signal force un processus à se terminer, le processus parent peut déterminer la cause de la terminaison en examinant le code de terminaison signalé par les fonctions 'wait' et 'waitpid'. Les informations pouvant être obtenues incluent le fait que le processus s'est terminé par un signal ainsi que le type de signal. Si un programme que vous exécutez à partir de la ligne de commande est terminé par un signal, le shell affiche généralement des messages d'erreur. Les signaux qui représentent normalement des erreurs de programme ont une propriété spéciale: lorsqu'un de ces signaux termine le processus, il écrit également un fichier core dump qui enregistre le statut du processus à la fin. Vous pouvez examiner le fichier avec un débogueur pour déterminer la cause de l'erreur. Si vous générez un signal, qui est une erreur de programme, par une requête explicite et que le processus se termine, le fichier est généré comme si le signal avait été généré par une erreur.

Si un signal est envoyé au processus alors qu'il exécute un appel système bloqueur , le processus suspend l'appel, exécute le gestionnaire de signaux défini à l'aide de signal et alors l'opération échouera (avec “errno” sur “EINTR”) ou l'opération sera redémarrée. Les systèmes System V se comportent comme dans le premier cas, BSD comme dans le second. Depuis la glibc v2, le comportement est le même que sous BSD, tout dépend de la définition de _BSD_SOURCE mackerel. Le comportement peut être contrôlé par le programmeur en utilisant sigaction avec l'indicateur SA_RESTART .

Types standard de signaux

Cette section répertorie les noms de différents types de signaux standard et décrit le type d’événements qu’il indique.

Chaque nom de signal est un macrodefined , qui est en fait un nombre entier positif (le nombre correspondant à ce type de signal).

Un programme ne devrait jamais émettre d'hypothèses sur le code numérique d'un type de signal particulier, mais plutôt s'y référer toujours par son nom. En effet, un numéro pour un type de signal peut varier d'un système à un autre, mais leurs noms sont standard. Pour obtenir la liste complète des signaux pris en charge par un système, vous pouvez exécuter la ligne de commande:

$ 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     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      33) SIGRTMIN    34) SIGRTMIN+1
    35) SIGRTMIN+2  36) SIGRTMIN+3  37) SIGRTMIN+4  38) SIGRTMIN+5
    39) SIGRTMIN+6  40) SIGRTMIN+7  41) SIGRTMIN+8  42) SIGRTMIN+9
    43) SIGRTMIN+10 44) SIGRTMIN+11 45) SIGRTMIN+12 46) SIGRTMIN+13
    47) SIGRTMIN+14 48) SIGRTMIN+15 49) SIGRTMAX-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

Les noms des signaux sont définis dans l'en-tête signal.h . Généralement, les signaux ont des rôles prédéfinis, mais ils peuvent être écrasés par le programmeur. Les plus communs sont les signaux suivants:

  • SIGINT - transmis au toucher de la combinaison CTRL + C ;
  • SIGQUIT - transmis en appuyant sur la combinaison de touches CTRL + \ ;
  • SIGSEGV - transmis lors de l'accès à un emplacement mémoire invalide, etc.
  • SIGKILL - ne peut être ignoré ni écrasé. La transmission de ce signal a pour effet de mettre fin au processus, quel que soit le contexte.

Les messages décrivant des signaux

Le meilleur moyen d’afficher une description d’un signal est d’utiliser le strsignal et le psignal. Ces fonctions utilisent un numéro de signal pour spécifier le type de signal à décrire. Vous trouverez ci-dessous un exemple d'utilisation de ces fonctionnalités:

msg_signal.c
#include <stdio.h>
#include <stdlib.h>
 
#define __USE_GNU
#include <string.h>
 
#include <signal.h>
 
int main(void) {
    char *sig_p = strsignal(SIGKILL);
 
    printf("signal %d is %s\n", SIGKILL, sig_p);
 
    psignal(SIGKILL, "death and decay");
 
    return 0;
}

Pour compiler et exécuter la séquence, procédez comme suit:

so@spook$ gcc -Wall -g -o msg_signal msg_signal.c
so@spook$ ./msg_signal 
signal 9 is Killed
death and decay: Killed

Masques de signalisation. Verrouillage du signal

Afin de pouvoir effectuer des opérations de blocage / déblocage de signal, nous devons connaître l'état de chaque signal à chaque étape du flux d'exécution. Le système d’exploitation a également besoin de la même chose pour décider du signal à envoyer à un processus (il a besoin de ce type d’informations séparément pour chaque processus). À cette fin, un masque de signaux est utilisé pour chaque processus.

Le masque de signal a chaque bit associé à un type de signal. </note> Le masque binaire est utilisé par plusieurs fonctions, y compris la fonction sigprocmask utilisée pour modifier le masque de signal de processus. en cours. <code c> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); </code> Le type de données utilisé par les systèmes UNIX pour représenter les masques de signaux est sigset_t . Les variables de ce type sont 'non initialisées' . Les opérations sur ce type de données sont: * démarrage 0; * initialisation de démarrage de 1; * bloquer un signal; * déverrouiller un signal; * détection du blocage du signal. Les fonctions suivantes permettent de manipuler le masque de bits. Ils ne décident pas de l'action de bloquer ou de déverrouiller un signal, mais placent simplement le signal dans le masque binaire (ajoutez le bit correspondant au signal à 1 et supprimez-le à 0), puis utilisez sigprocmask pour définir l’action de verrouillage / déverrouillage effectif. Vous pouvez trouver plus de détails sur ces fonctionnalités aici. <code c> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigismember(sigset_t *set, int signo); </code> <note warning> Avant d'utiliser les fonctions 'sigaddset', 'sigdelset' et 'sigismember' sur un sigset_t, ce type doit être initialisé à l'aide de gemptset ou sigfillset . Le comportement n'est pas défini autrement. </note> L'équation ci-dessous est un cas d'utilisation de la fonction du masque de signal, dans laquelle, toutes les 5 secondes, le signal SIGINT est verrouillé / déverrouillé: <code c> sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); while (1) { sleep(5); sigprocmask(SIG_BLOCK, &set, NULL); sleep(5); sigprocmask(SIG_UNBLOCK, &set, NULL); } </code> Une autre valeur que le premier paramètre de sigprocmask peut prendre est le SIG_SETMASK , qui spécifie clairement simplement que l'ancien masque (le troisième paramètre) est remplacé par le deuxième paramètre (le nouveau masque). Un exemple d'utilisation est disponible à l'adresse https://support.sas.com/documentation/onlinedoc/sasc/doc750/html/lr1/zlocking.htm "cette adresse. ===== Traitement du signal ===== Le traitement des signaux se fait en associant une fonction ( gestionnaire **) à un signal. La fonction sera appelée lorsque le processus recevra le signal. Traditionnellement, la fonction utilisée pour associer les gestionnaires pour traiter un signal était signal. Pour remédier aux faiblesses de cette fonctionnalité, le standard POSIX a défini la fonction sigaction pour associer un gestionnaire à un signal. La “sigaction” offre plus de contrôle au prix d'un degré de complexité plus élevé.

 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

Le composant important de la fonction sigaction est la structure du même nom, décrite dans la page de la fonction manuelle:

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
};

Si le champ sa_flags spécifie l'indicateur SA_SIGINFO , le gestionnaire utilisé est celui spécifié par sa_sigaction . Sinon, le gestionnaire utilisé est sa_handler . Le masque des signaux qui doivent être bloqués lors de l'exécution du gestionnaire est représenté par sa_mask .

Un exemple d'association d'un gestionnaire de signal est présenté ci-dessous:

#include <signal.h>
...
 
/* SIGUSR2 handler */
static void usr2_handler(int signum) {
    /* actions that should be taken when the signal signum is received */
    ...
}
 
int main(void) {
    struct sigaction sa;
 
    memset(&sa, 0, sizeof(sa));
 
    sa.sa_flags   = SA_RESETHAND;   /* restore handler to previous state */
    sa.sa_handler = usr2_handler;
    sigaction(SIGUSR2, &sa, NULL);
 
    return 0;
}

Vous pouvez choisir de configurer votre propre gestionnaire ou d’utiliser un gestionnaire prédéfini. Vous pouvez utiliser SIG_IGN pour ignorer le signal ou SIG_DFL pour exécuter l'action par défaut (mettre fin au processus, ignorer le signal, etc.).

Détails de la structure siginfo_t

Détails de la structure siginfo_t

Si le drapeau SA_SIGINFO est défini, le sa_sigaction de la structure 'sigaction' est utilisé pour spécifier le gestionnaire associé au signal. Le gestionnaire utilisé dans ce cas reçoit trois paramètres et peut être utilisé pour transmettre des informations utiles avec le processus. Le troisième argument ('void *') est rarement utilisé. Le deuxième argument, de type siginfo_t, définit une structure contenant des informations utiles sur le contexte de l'occurrence du signal et d'autres informations pouvant être fournies par le programmeur. La définition de la structure se trouve dans la page de manuel de la fonction sigaction.

siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               long     si_band;     /* Band event (was int in
                                        glibc 2.3.2 and earlier) */
               int      si_fd;       /* File descriptor */
               short    si_addr_lsb; /* Least significant bit of address
                                        (since kernel 2.6.32) */
}

Les membres de la structure ne sont initialisés que lorsque leurs valeurs sont utiles. Les membres si_signo , si_errno et and_code sont toujours définis pour tous les signaux. Le reste de la structure peut être une union, de sorte que seuls les champs qui détectent le signal reçu doivent être lus. Par exemple, l'appel système 'kill', les signaux POSIX.1b et '' SIGCHLD 'complètent' 'si_pid' 'et' 'si_uid' ', et' SIGILL, SIGFPE, SIGSEGV 'et' SIGBUS ' 'complète' 'and_addr' 'avec l'adresse à l'origine de l'erreur.

===== Rapports de processus =====

sde/laboratoare/05.1553013857.txt.gz · Last modified: 2019/03/19 18:44 by iuliana.marin
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0