Gestion des signaux

Les signaux dans les processus multifiletés sont une extension des signaux dans les programmes monofiletés traditionnels.

La gestion des signaux dans les processus à unités d'exécution multiples est partagée par les niveaux de processus et d'unité d'exécution et comprend les éléments suivants:
  • Gestionnaires de signaux par processus
  • Masques de signal par unité d'exécution
  • Distribution unique de chaque signal

Gestionnaires de signaux et masques de signal

Les gestionnaires de signaux sont maintenus au niveau du processus. Il est fortement recommandé d'utiliser la sous-routine sigwait lors de l'attente de signaux. La sous-routine sigaction n'est pas recommandée car la liste des gestionnaires de signaux est gérée au niveau du processus et toute unité d'exécution du processus peut la modifier. Si deux unités d'exécution définissent un gestionnaire de signaux sur le même signal, la dernière unité d'exécution qui a appelé la sous-routine sigaction remplace le paramètre de l'appel d'unité d'exécution précédent et, dans la plupart des cas, l'ordre dans lequel les unités d'exécution sont planifiées ne peut pas être prédit.

Les masques de signal sont maintenus au niveau de l'unité d'exécution. Chaque unité d'exécution peut avoir son propre ensemble de signaux qui seront bloqués à la distribution. La sous-routine sigthreadmask doit être utilisée pour obtenir et définir le masque de signal de l'unité d'exécution appelante. La sous-routine sigprocmask ne doit pas être utilisée dans des programmes à unités d'exécution multiples, car un comportement inattendu peut se produire.

La sous-routine pthread_sigmask est très similaire à la sous-routine sigprocmask . Les paramètres et l'utilisation des deux sous-routines sont identiques. Lors du portage de code existant pour la prise en charge de la bibliothèque d'unités d'exécution, vous pouvez remplacer la sous-routine sigprocmask par la sous-routine pthread_sigmask .

Génération de signaux

Les signaux générés par une action attribuable à une unité d'exécution particulière, telle qu'une panne matérielle, sont envoyés à l'unité d'exécution qui a généré le signal. Les signaux générés en association avec un ID de processus, un ID de groupe de processus ou un événement asynchrone (tel qu'une activité de terminal) sont envoyés au processus.

  • La sous-routine pthread_kill envoie un signal à une unité d'exécution. Etant donné que les ID d'unité d'exécution identifient les unités d'exécution d'un processus, cette sous-routine ne peut envoyer des signaux qu'aux unités d'exécution du même processus.
  • La sous-routine kill (et donc la commande kill ) envoie un signal à un processus. Une unité d'exécution peut envoyer un signal Signal à son processus en exécutant l'appel suivant:
    kill(getpid(), Signal);
  • La sous-routine raise ne peut pas être utilisée pour envoyer un signal au processus de l'unité d'exécution appelante. La sous-routine raise envoie un signal à l'unité d'exécution appelante, comme dans l'appel suivant:
    pthread_kill(pthread_self(), Signal);
    Cela permet de s'assurer que le signal est envoyé à l'appelant de la sous-routine raise . Ainsi, les routines de bibliothèque écrites pour des programmes à unité d'exécution unique peuvent facilement être portées vers un système à unités d'exécution multiples, car la sous-routine raise est généralement destinée à envoyer le signal à l'appelant.
  • La sous-routine alarm demande qu'un signal soit envoyé ultérieurement au processus et que les états d'alarme soient conservés au niveau du processus. Ainsi, la dernière unité d'exécution qui a appelé la sous-routine alarm remplace les paramètres des autres unités d'exécution du processus. Dans un programme à unités d'exécution multiples, le signal SIGALRM n'est pas nécessairement transmis à l'unité d'exécution qui a appelé la sous-routine alarm . L'unité d'exécution appelante peut même être arrêtée ; par conséquent, elle ne peut pas recevoir le signal.

Traitement des signaux

Les gestionnaires de signaux sont appelés dans l'unité d'exécution à laquelle le signal est délivré. Les limitations suivantes des gestionnaires de signaux sont introduites par la bibliothèque d'unités d'exécution:
  • Les gestionnaires de signaux peuvent appeler la sous-routine longjmp ou siglongjmp uniquement si l'appel correspondant à la sous-routine setjmp ou sigsetjmp est effectué dans la même unité d'exécution.

    Généralement, un programme qui souhaite attendre un signal installe un gestionnaire de signaux qui appelle la sous-routine longjmp pour continuer l'exécution au point où la sous-routine setjmp correspondante est appelée. Cette opération ne peut pas être effectuée dans un programme à unités d'exécution multiples, car le signal peut être envoyé à une unité d'exécution autre que celle qui a appelé la sous-routine setjmp , ce qui entraîne l'exécution du gestionnaire par une unité d'exécution incorrecte.

    Remarque: l'utilisation de longjmp à partir d'un gestionnaire de signaux peut entraîner un comportement non défini.
  • Aucune routine pthread ne peut être appelée à partir d'un gestionnaire de signaux. L'appel d'une routine pthread à partir d'un gestionnaire de signaux peut entraîner un blocage de l'application.

Pour permettre à une unité d'exécution d'attendre des signaux générés de manière asynchrone, la bibliothèque d'unités d'exécution fournit la sous-routine sigwait . La sous-routine sigwait bloque l'unité d'exécution appelante jusqu'à ce que l'un des signaux attendus soit envoyé au processus ou à l'unité d'exécution. Aucun gestionnaire de signaux ne doit être installé sur le signal attendu à l'aide de la sous-routine sigwait .

Généralement, les programmes peuvent créer une unité d'exécution dédiée pour attendre des signaux générés de manière asynchrone. Une telle unité d'exécution boucle sur un appel de sous-routine sigwait et gère les signaux. Il est recommandé qu'un tel fil bloque tous les signaux. Le fragment de code suivant donne un exemple d'une telle unité d'exécution d'unité d'attente de signal:
#include <pthread.h>
#include <signal.h>

static pthread_mutex_t mutex;
sigset_t set;
static int sig_cond = 0;

void *run_me(void *id)
{
       int sig;
       int err;
       sigset_t sigs;
       sigset_t oldSigSet;
       sigfillset(&sigs);
       sigthreadmask(SIG_BLOCK, &sigs, &oldSigSet);

       err = sigwait(&set, &sig);

       if(err)
       {
              /* do error code */
       }
       else
       {
              printf("SIGINT caught\n");
              pthread_mutex_lock(&mutex);
              sig_cond = 1;
              pthread_mutex_unlock(&mutex);
       }

       return;
}

main()
{
       pthread_t tid;

       sigemptyset(&set);
       sigaddset(&set, SIGINT); 
       pthread_sigmask(SIG_BLOCK, &set, 0);

       pthread_mutex_init(&mutex, NULL);

       pthread_create(&tid, NULL, run_me, (void *)1);

       while(1)
       {
              sleep(1);
              /* or so something here */

              pthread_mutex_lock(&mutex);
              if(sig_cond)
              {
                     /* do exit stuff */
                     return;
              }
              pthread_mutex_unlock(&mutex);

       }

}

Si plusieurs unités d'exécution ont appelé la sous-routine sigwait , un seul appel est renvoyé lorsqu'un signal correspondant est envoyé. L'unité d'exécution qui est réveillée ne peut pas être prédite. Si une unité d'exécution est sur le point d'exécuter la commande sigwait ainsi que le traitement de certains autres signaux pour lesquels elle n'effectue pas la commande sigwait, les gestionnaires de signaux définis par l'utilisateur doivent bloquer les signaux de l'unité d'exécution pour le traitement approprié. Notez que la sous-routine sigwait fournit un point d'annulation.

Etant donné qu'une unité d'exécution dédiée n'est pas un véritable gestionnaire de signaux, elle peut signaler une condition à toute autre unité d'exécution. Il est possible d'implémenter une routine sigwait_multiple qui réveillerait toutes les unités d'exécution en attente d'un signal spécifique. Chaque appelant de la routine sigwait_multiple enregistre un ensemble de signaux. L'appelant attend alors une variable de condition. Une seule unité d'exécution appelle la sous-routine sigwait sur l'union de tous les signaux enregistrés. Lorsque l'appel à la sous-routine sigwait est renvoyé, l'état approprié est défini et les variables de condition sont diffusées. Les nouveaux appelants de la sous-routine sigwait_multiple entraînent l'annulation de l'appel de la sous-routine sigwait en attente et sa réémission pour mettre à jour l'ensemble des signaux en attente.

Envoi de signaux

Un signal est envoyé à une unité d'exécution, sauf si son action est définie sur Ignorer. Les règles suivantes régissent la distribution des signaux dans un processus à unités d'exécution multiples:

  • Un signal dont l'action est définie pour arrêter, arrêter ou continuer l'unité d'exécution ou le processus cible s'arrête, s'arrête ou poursuit l'ensemble du processus (et donc toutes ses unités d'exécution). Les programmes à unité d'exécution unique peuvent ainsi être réécrits en tant que programmes à unités d'exécution multiples sans changer leur comportement de signal visible en externe.

    Prenons l'exemple d'une commande utilisateur à unités d'exécution multiples, telle que la commande grep . Un utilisateur peut démarrer la commande dans son shell favori, puis décider de l'arrêter en envoyant un signal à l'aide de la commande kill . Le signal doit arrêter l'ensemble du processus exécutant la commande grep .

  • Les signaux générés pour une unité d'exécution spécifique, à l'aide de pthread_kill ou des sous-routines raise , sont distribués à cette unité d'exécution. Si l'unité d'exécution a bloqué le signal de la distribution, le signal est mis en attente sur l'unité d'exécution jusqu'à ce que le signal soit débloqué de la distribution. Si l'unité d'exécution est arrêtée avant la distribution du signal, le signal sera ignoré.
  • Les signaux générés pour un processus, à l'aide de la sous-routine kill par exemple, sont distribués à une seule unité d'exécution du processus. Si une ou plusieurs unités d'exécution ont appelé la sous-routine sigwait , le signal est envoyé à une seule de ces unités d'exécution. Dans le cas contraire, le signal est délivré à exactement une unité d'exécution qui n'a pas bloqué le signal de la distribution. Si aucune unité d'exécution ne correspond à ces conditions, le signal est mis en attente sur le processus jusqu'à ce qu'une unité d'exécution appelle la sous-routine sigwait en spécifiant ce signal ou qu'une unité d'exécution débloque le signal de la distribution.

Si l'action associée à un signal en attente (sur une unité d'exécution ou sur un processus) est définie sur Ignorer, le signal est ignoré.