Comment éviter d'utiliser printf dans un gestionnaire de signaux?

86

Puisqu'il printfn'est pas réentrant, il n'est pas censé être sûr de l'utiliser dans un gestionnaire de signaux. Mais j'ai vu beaucoup d'exemples de codes qui utilisent printfcette façon.

Ma question est donc la suivante: quand devons-nous éviter d'utiliser printfdans un gestionnaire de signaux, et y a-t-il un remplacement recommandé?

Yu Hao
la source
12
Une réponse simple et pas très utile à la question de votre titre: voyez cet printfappel dans ce signal hander? Supprime-le.
Keith Thompson
6
Bonjour Yu Hao! Je pense que vous trouverez le lien très intéressant à lire. "Utilisez des fonctions réentrantes pour une gestion plus sûre du signal" Je l'ai lu après si longtemps, je voudrais partager l'artificiel ici avec vous. Espérons que vous apprécierez.
Grijesh Chauhan

Réponses:

58

Vous pouvez utiliser une variable d'indicateur, définir cet indicateur dans le gestionnaire de signal et en fonction de cette printf()fonction d' appel d'indicateur dans main () ou dans une autre partie du programme pendant le fonctionnement normal.

Il n'est pas sûr d'appeler toutes les fonctions, telles que printf, à partir d'un gestionnaire de signaux. Une technique utile consiste à utiliser un gestionnaire de signaux pour définir un flag, puis le vérifier à flag partir du programme principal et imprimer un message si nécessaire.

Remarquez dans l'exemple ci-dessous, le gestionnaire de signaux ding () a mis un drapeau alarm_firedà 1 comme SIGALRM capturé et dans la alarm_firedvaleur de la fonction principale est examinée pour appeler correctement printf de manière conditionnelle.

static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
  alarm_fired = 1; // set flag
}
int main()
{
    pid_t pid;
    printf("alarm application starting\n");
    pid = fork();
    switch(pid) {
        case -1:
            /* Failure */
            perror("fork failed");
            exit(1);
        case 0:
            /* child */
            sleep(5);
            kill(getppid(), SIGALRM);
            exit(0);
    }
    /* if we get here we are the parent process */
    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)  // check flag to call printf
      printf("Ding!\n");
    printf("done\n");
    exit(0);
}

Référence: Début de la programmation Linux, 4e édition , Dans ce livre, exactement votre code est expliqué (ce que vous voulez), Chapitre 11: Processus et signaux, page 484

De plus, vous devez faire particulièrement attention lors de l'écriture des fonctions de gestionnaire car elles peuvent être appelées de manière asynchrone. Autrement dit, un gestionnaire peut être appelé à n'importe quel moment du programme, de manière imprévisible. Si deux signaux arrivent pendant un intervalle très court, un gestionnaire peut fonctionner dans un autre. Et il est considéré comme une meilleure pratique de déclarer volatile sigatomic_t, ce type est toujours accessible de manière atomique, évitez toute incertitude sur l'interruption de l'accès à une variable. (lire: Accès aux données atomiques et traitement du signal pour une expiation détaillée).

Lisez Définition des gestionnaires de signaux : pour apprendre à écrire une fonction de gestionnaire de signaux pouvant être établie avec les fonctions signal()ou sigaction().
Liste des fonctions autorisées dans la page de manuel , l'appel de cette fonction à l'intérieur du gestionnaire de signaux est sûr.

Grijesh Chauhan
la source
18
Il est préférable de déclarervolatile sigatomic_t alarm_fired;
Basile Starynkevitch
1
@GrijeshChauhan: si nous travaillons dans un code de produit, alors nous ne pouvons pas appeler la fonction de pause, le flux peut être n'importe où lorsque le signal se produit, donc dans ce cas nous ne savons vraiment pas où garder "if (alarm_fired) printf (" Ding! \ n ");" dans du code.
pankaj kushwaha
@pankajkushwaha oui, vous avez raison, il souffre d'une condition de race
Grijesh Chauhan
@GrijeshChauhan, Il y a deux choses que je ne pouvais pas comprendre. 1. Comment savez-vous quand vérifier le drapeau? Il y aura donc plusieurs points de contrôle dans le code à presque tous les points à imprimer. Il y aura certainement des conditions de course où le signal pourrait être appelé avant l'enregistrement du signal ou le signal pourrait se produire après le point de contrôle. Je pense que cela aidera uniquement l'impression dans certaines conditions, mais ne résoudra pas complètement le problème.
Darshan b
52

Le problème principal est que si le signal interrompt malloc()ou une fonction similaire, l'état interne peut être temporairement incohérent pendant qu'il déplace des blocs de mémoire entre la liste libre et utilisée, ou d'autres opérations similaires. Si le code du gestionnaire de signaux appelle une fonction qui appelle ensuite malloc(), cela peut complètement détruire la gestion de la mémoire.

La norme C adopte une vision très conservatrice de ce que vous pouvez faire dans un gestionnaire de signaux:

ISO / CEI 9899: 2011 §7.14.1.1 La signalfonction

¶5 Si le signal se produit autrement qu'à la suite de l'appel de la fonction abortou raise, le comportement n'est pas défini si le gestionnaire de signal fait référence à un objet avec une durée de stockage statique ou de thread qui n'est pas un objet atomique sans verrouillage autrement qu'en attribuant une valeur à un objet déclaré comme volatile sig_atomic_t, ou le gestionnaire de signal appelle toute fonction de la bibliothèque standard autre que la abortfonction, la _Exitfonction, la quick_exitfonction ou la signalfonction avec le premier argument égal au numéro de signal correspondant au signal qui a provoqué l'appel du gestionnaire. De plus, si un tel appel à la signalfonction aboutit à un SIG_ERRretour, la valeur de errnoest indéterminée. 252)

252) Si un signal est généré par un gestionnaire de signal asynchrone, le comportement n'est pas défini.

POSIX est beaucoup plus généreux sur ce que vous pouvez faire dans un gestionnaire de signaux.

Signal Concepts dans l'édition POSIX 2008 dit:

Si le processus est multithread, ou si le processus est monothread et qu'un gestionnaire de signaux est exécuté autrement que par:

  • L'appel de processus abort(), raise(), kill(), pthread_kill()ou sigqueue()pour générer un signal qui est pas bloqué

  • Un signal en attente étant débloqué et délivré avant l'appel qui l'a débloqué, il revient

le comportement n'est pas défini si le gestionnaire de signal fait référence à un objet autre que celui errnoavec une durée de stockage statique autrement qu'en attribuant une valeur à un objet déclaré comme volatile sig_atomic_t, ou si le gestionnaire de signal appelle une fonction définie dans cette norme autre que l'une des fonctions répertoriées dans le tableau suivant.

Le tableau suivant définit un ensemble de fonctions qui doivent être sécurisées pour les signaux asynchrones. Par conséquent, les applications peuvent les invoquer, sans restriction, à partir des fonctions de capture de signal:

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()
…
fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

Toutes les fonctions qui ne figurent pas dans le tableau ci-dessus sont considérées comme dangereuses par rapport aux signaux. En présence de signaux, toutes les fonctions définies par ce volume de POSIX.1-2008 doivent se comporter comme défini lorsqu'elles sont appelées ou interrompues par une fonction de capture de signal, à une seule exception: lorsqu'un signal interrompt une fonction non sûre et le signal- catching function appelle une fonction unsafe, le comportement n'est pas défini.

Les opérations qui obtiennent la valeur de errnoet les opérations qui attribuent une valeur à errnodoivent être sans danger pour les signaux asynchrones.

Lorsqu'un signal est délivré à un thread, si l'action de ce signal spécifie la fin, l'arrêt ou la poursuite, l'ensemble du processus doit être terminé, arrêté ou poursuivi, respectivement.

Cependant, la printf()famille de fonctions est notablement absente de cette liste et peut ne pas être appelée en toute sécurité depuis un gestionnaire de signaux.

La mise à jour POSIX 2016 étend la liste des fonctions sûres pour inclure, en particulier, un grand nombre de fonctions <string.h>, ce qui est un ajout particulièrement précieux (ou était un oubli particulièrement frustrant). La liste est maintenant:

_Exit()              getppid()            sendmsg()            tcgetpgrp()
_exit()              getsockname()        sendto()             tcsendbreak()
abort()              getsockopt()         setgid()             tcsetattr()
accept()             getuid()             setpgid()            tcsetpgrp()
access()             htonl()              setsid()             time()
aio_error()          htons()              setsockopt()         timer_getoverrun()
aio_return()         kill()               setuid()             timer_gettime()
aio_suspend()        link()               shutdown()           timer_settime()
alarm()              linkat()             sigaction()          times()
bind()               listen()             sigaddset()          umask()
cfgetispeed()        longjmp()            sigdelset()          uname()
cfgetospeed()        lseek()              sigemptyset()        unlink()
cfsetispeed()        lstat()              sigfillset()         unlinkat()
cfsetospeed()        memccpy()            sigismember()        utime()
chdir()              memchr()             siglongjmp()         utimensat()
chmod()              memcmp()             signal()             utimes()
chown()              memcpy()             sigpause()           wait()
clock_gettime()      memmove()            sigpending()         waitpid()
close()              memset()             sigprocmask()        wcpcpy()
connect()            mkdir()              sigqueue()           wcpncpy()
creat()              mkdirat()            sigset()             wcscat()
dup()                mkfifo()             sigsuspend()         wcschr()
dup2()               mkfifoat()           sleep()              wcscmp()
execl()              mknod()              sockatmark()         wcscpy()
execle()             mknodat()            socket()             wcscspn()
execv()              ntohl()              socketpair()         wcslen()
execve()             ntohs()              stat()               wcsncat()
faccessat()          open()               stpcpy()             wcsncmp()
fchdir()             openat()             stpncpy()            wcsncpy()
fchmod()             pause()              strcat()             wcsnlen()
fchmodat()           pipe()               strchr()             wcspbrk()
fchown()             poll()               strcmp()             wcsrchr()
fchownat()           posix_trace_event()  strcpy()             wcsspn()
fcntl()              pselect()            strcspn()            wcsstr()
fdatasync()          pthread_kill()       strlen()             wcstok()
fexecve()            pthread_self()       strncat()            wmemchr()
ffs()                pthread_sigmask()    strncmp()            wmemcmp()
fork()               raise()              strncpy()            wmemcpy()
fstat()              read()               strnlen()            wmemmove()
fstatat()            readlink()           strpbrk()            wmemset()
fsync()              readlinkat()         strrchr()            write()
ftruncate()          recv()               strspn()
futimens()           recvfrom()           strstr()
getegid()            recvmsg()            strtok_r()
geteuid()            rename()             symlink()
getgid()             renameat()           symlinkat()
getgroups()          rmdir()              tcdrain()
getpeername()        select()             tcflow()
getpgrp()            sem_post()           tcflush()
getpid()             send()               tcgetattr()

En conséquence, vous finissez par utiliser write()sans le support de formatage fourni par printf()et al, ou vous finissez par définir un indicateur que vous testez (périodiquement) aux endroits appropriés dans votre code. Cette technique est habilement démontrée dans la réponse de Grijesh Chauhan .


Fonctions standard C et sécurité des signaux

chqrlie pose une question intéressante, à laquelle je n'ai qu'une réponse partielle:

Comment se fait-il que la plupart des fonctions de chaîne <string.h>ou les fonctions de classe de caractères <ctype.h>et de nombreuses autres fonctions de bibliothèque standard C ne figurent pas dans la liste ci-dessus? Une implémentation doit être délibérément mauvaise pour rendre strlen()l'appel d'un gestionnaire de signaux dangereux.

Pour la plupart des fonctions <string.h>, il est difficile de voir pourquoi ils ont pas été déclarés en sécurité signal async, et je suis d' accord l' strlen()est un bon exemple, avec strchr(), strstr(), etc. D'autre part, d' autres fonctions telles que strtok(), strcoll()et strxfrm()sont plutôt complexes et ne sont probablement pas sûrs pour les signaux asynchrones. Parce que strtok()conserve l'état entre les appels, et le gestionnaire de signal ne pouvait pas facilement dire si une partie du code utilisé strtok()serait gâchée. Les fonctions strcoll()et strxfrm()fonctionnent avec des données sensibles aux paramètres régionaux, et le chargement des paramètres régionaux implique toutes sortes de paramètres d'état.

Les fonctions (macros) de <ctype.h>sont toutes sensibles aux paramètres régionaux et peuvent donc rencontrer les mêmes problèmes que strcoll()et strxfrm().

J'ai du mal à comprendre pourquoi les fonctions mathématiques de <math.h>ne sont pas sûres pour les signaux asynchrones, à moins que ce ne soit parce qu'elles pourraient être affectées par un SIGFPE (exception en virgule flottante), bien que la seule fois où je vois l'une de ces dernières de nos jours est pour un entier division par zéro. Une incertitude similaire provient de <complex.h>, <fenv.h>et <tgmath.h>.

Certaines fonctions de <stdlib.h>peuvent être exemptées, abs()par exemple. D'autres sont particulièrement problématiques: malloc()et la famille en sont les premiers exemples.

Une évaluation similaire pourrait être faite pour les autres en-têtes de la norme C (2011) utilisés dans un environnement POSIX. (Le Standard C est tellement restrictif qu'il n'y a aucun intérêt à les analyser dans un environnement Standard C pur.) Ceux marqués comme `` dépendants de la locale '' ne sont pas sûrs car la manipulation des paramètres régionaux peut nécessiter une allocation de mémoire, etc.

  • <assert.h>- Probablement pas sûr
  • <complex.h>- Peut - être sûr
  • <ctype.h> - Pas sécurisé
  • <errno.h> - Sûr
  • <fenv.h>- Probablement pas sûr
  • <float.h> - Aucune fonction
  • <inttypes.h> - Fonctions sensibles aux paramètres régionaux (non sécurisées)
  • <iso646.h> - Aucune fonction
  • <limits.h> - Aucune fonction
  • <locale.h> - Fonctions sensibles aux paramètres régionaux (non sécurisées)
  • <math.h>- Peut - être sûr
  • <setjmp.h> - Pas sécurisé
  • <signal.h> - Permis
  • <stdalign.h> - Aucune fonction
  • <stdarg.h> - Aucune fonction
  • <stdatomic.h>- Peut - être sûr, probablement pas sûr
  • <stdbool.h> - Aucune fonction
  • <stddef.h> - Aucune fonction
  • <stdint.h> - Aucune fonction
  • <stdio.h> - Pas sécurisé
  • <stdlib.h> - Pas tous sûrs (certains sont autorisés, d'autres non)
  • <stdnoreturn.h> - Aucune fonction
  • <string.h> - Pas tout en sécurité
  • <tgmath.h>- Peut - être sûr
  • <threads.h>- Probablement pas sûr
  • <time.h>- Dépend des paramètres régionaux (mais time()est explicitement autorisé)
  • <uchar.h> - Dépend des paramètres régionaux
  • <wchar.h> - Dépend des paramètres régionaux
  • <wctype.h> - Dépend des paramètres régionaux

Analyser les en-têtes POSIX serait… plus difficile car il y en a beaucoup, et certaines fonctions peuvent être sûres mais beaucoup ne le seront pas… mais aussi plus simple parce que POSIX dit quelles fonctions sont sûres pour les signaux asynchrones (pas beaucoup d'entre elles). Notez qu'un en-tête comme <pthread.h>a trois fonctions sûres et de nombreuses fonctions non sûres.

NB: La quasi-totalité de l'évaluation des fonctions C et des en-têtes dans un environnement POSIX est une approximation semi-instruite. Cela n'a aucun sens une déclaration définitive d'un organisme de normalisation.

Jonathan Leffler
la source
Comment se fait-il que la plupart des fonctions de chaîne <string.h>ou les fonctions de classe de caractères <ctype.h>et de nombreuses autres fonctions de bibliothèque standard C ne figurent pas dans la liste ci-dessus? Une implémentation devrait être délibérément mauvaise pour rendre strlen()l'appel d'un gestionnaire de signaux dangereux.
chqrlie
@chqrlie: question intéressante - voir la mise à jour (il n'y avait aucun moyen d'intégrer cela de manière raisonnable dans les commentaires).
Jonathan Leffler
Merci pour votre analyse approfondie. En ce qui concerne le <ctype.h>contenu, il est spécifique à la locale et pourrait causer des problèmes si le signal interrompt une fonction de réglage de la locale, mais une fois que la locale est chargée, leur utilisation devrait être sûre. Je suppose que, dans certaines situations complexes, le chargement des données locales pourrait être effectué de manière incrémentielle, rendant ainsi les fonctions <ctype.h>dangereuses. La conclusion demeure: en cas de doute, abstenez-vous.
chqrlie
@chqrlie: Je suis d'accord que la morale de l'histoire devrait être En cas de doute, abstenez-vous . C'est un beau résumé.
Jonathan Leffler
13

Comment éviter d'utiliser printfdans un gestionnaire de signaux?

  1. Évitez toujours cela, dira: Ne l'utilisez pas printf()dans les gestionnaires de signaux.

  2. Au moins sur les systèmes conformes POSIX, vous pouvez utiliser à la write(STDOUT_FILENO, ...)place deprintf() . Le formatage peut cependant ne pas être facile: Imprimez int à partir du gestionnaire de signaux à l'aide de fonctions d'écriture ou de sécurité asynchrone

alk
la source
1
Alk Always avoid it.signifie? A éviter printf()?
Grijesh Chauhan
2
@GrijeshChauhan: Oui, car l'OP demandait quand éviter d'utiliser printf()dans les gestionnaires de signaux.
alk
Alk +1 pour le 2point, cochez OP demandant Comment éviter d'utiliser printf()dans les gestionnaires de signaux?
Grijesh Chauhan
7

À des fins de débogage, j'ai écrit un outil qui vérifie que vous n'appelez en fait que les fonctions de la async-signal-safeliste et affiche un message d'avertissement pour chaque fonction non sécurisée appelée dans un contexte de signal. Bien que cela ne résout pas le problème de vouloir appeler des fonctions non asynchrones à partir d'un contexte de signal, cela vous aide au moins à trouver des cas où vous l'avez fait accidentellement.

Le code source est sur GitHub . Il fonctionne en surchargeant signal/sigaction, puis en détournant temporairement les PLTentrées des fonctions non sécurisées; cela provoque la redirection des appels à des fonctions non sécurisées vers un wrapper.

dwks
la source
Demande de fonctionnalité GCC: gcc.gnu.org/ml/gcc-help/2012-03/msg00210.html
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1

Implémentez votre propre sécurité du signal asynchrone snprintf("%det utilisezwrite

Ce n'est pas aussi mauvais que je le pensais, comment convertir un int en chaîne en C?a plusieurs implémentations.

Puisqu'il n'y a que deux types de données intéressants auxquels les gestionnaires de signaux peuvent accéder:

  • sig_atomic_t globaux
  • int argument de signal

cela couvre essentiellement tous les cas d'utilisation intéressants.

Le fait que strcpy soit également un signal sûr rend les choses encore meilleures.

Le programme POSIX ci-dessous imprime sur stdout le nombre de fois qu'il a reçu SIGINT jusqu'à présent, que vous pouvez déclencher avec Ctrl + C , et l'ID de signal et.

Vous pouvez quitter le programme avec Ctrl + \ (SIGQUIT).

principal c:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

/* Calculate the minimal buffer size for a given type.
 *
 * Here we overestimate and reserve 8 chars per byte.
 *
 * With this size we could even print a binary string.
 *
 * - +1 for NULL terminator
 * - +1 for '-' sign
 *
 * A tight limit for base 10 can be found at:
 * /programming/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: /programming/190229/where-is-the-itoa-function-in-linux/52127877#52127877
 */
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2

/* async-signal-safe implementation of integer to string conversion.
 *
 * Null terminates the output string.
 *
 * The input buffer size must be large enough to contain the output,
 * the caller must calculate it properly.
 *
 * @param[out] value  Input integer value to convert.
 * @param[out] result Buffer to output to.
 * @param[in]  base   Base to convert to.
 * @return     Pointer to the end of the written string.
 */
char *itoa_safe(intmax_t value, char *result, int base) {
    intmax_t tmp_value;
    char *ptr, *ptr2, tmp_char;
    if (base < 2 || base > 36) {
        return NULL;
    }

    ptr = result;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
    } while (value);
    if (tmp_value < 0)
        *ptr++ = '-';
    ptr2 = result;
    result = ptr;
    *ptr-- = '\0';
    while (ptr2 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr2;
        *ptr2++ = tmp_char;
    }
    return result;
}

volatile sig_atomic_t global = 0;

void signal_handler(int sig) {
    char key_str[] = "count, sigid: ";
    /* This is exact:
     * - the null after the first int will contain the space
     * - the null after the second int will contain the newline
     */
    char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)];
    enum { base = 10 };
    char *end;
    end = buf;
    strcpy(end, key_str);
    end += sizeof(key_str);
    end = itoa_safe(global, end, base);
    *end++ = ' ';
    end = itoa_safe(sig, end, base);
    *end++ = '\n';
    write(STDOUT_FILENO, buf, end - buf);
    global += 1;
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    /* Unit test itoa_safe. */
    {
        typedef struct {
            intmax_t n;
            int base;
            char out[1024];
        } InOut;
        char result[1024];
        size_t i;
        InOut io;
        InOut ios[] = {
            /* Base 10. */
            {0, 10, "0"},
            {1, 10, "1"},
            {9, 10, "9"},
            {10, 10, "10"},
            {100, 10, "100"},
            {-1, 10, "-1"},
            {-9, 10, "-9"},
            {-10, 10, "-10"},
            {-100, 10, "-100"},

            /* Base 2. */
            {0, 2, "0"},
            {1, 2, "1"},
            {10, 2, "1010"},
            {100, 2, "1100100"},
            {-1, 2, "-1"},
            {-100, 2, "-1100100"},

            /* Base 35. */
            {0, 35, "0"},
            {1, 35, "1"},
            {34, 35, "Y"},
            {35, 35, "10"},
            {100, 35, "2U"},
            {-1, 35, "-1"},
            {-34, 35, "-Y"},
            {-35, 35, "-10"},
            {-100, 35, "-2U"},
        };
        for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
            io = ios[i];
            itoa_safe(io.n, result, io.base);
            if (strcmp(result, io.out)) {
                printf("%ju %d %s\n", io.n, io.base, io.out);
                assert(0);
            }
        }
    }

    /* Handle the signals. */
    if (argc > 1 && !strcmp(argv[1], "1")) {
        signal(SIGINT, signal_handler);
        while(1);
    }

    return EXIT_SUCCESS;
}

Compilez et exécutez:

gcc -std=c99 -Wall -Wextra -o main main.c
./main 1

Après avoir appuyé quinze fois sur Ctrl + C, le terminal affiche:

^Ccount, sigid: 0 2
^Ccount, sigid: 1 2
^Ccount, sigid: 2 2
^Ccount, sigid: 3 2
^Ccount, sigid: 4 2
^Ccount, sigid: 5 2
^Ccount, sigid: 6 2
^Ccount, sigid: 7 2
^Ccount, sigid: 8 2
^Ccount, sigid: 9 2
^Ccount, sigid: 10 2
^Ccount, sigid: 11 2
^Ccount, sigid: 12 2
^Ccount, sigid: 13 2
^Ccount, sigid: 14 2

2 est le numéro de signal pourSIGINT .

Testé sur Ubuntu 18.04. GitHub en amont .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
0

Une technique qui est particulièrement utile dans les programmes qui ont une boucle de sélection consiste à écrire un octet dans un tube à la réception d'un signal, puis à traiter le signal dans la boucle de sélection. Quelque chose du genre (gestion des erreurs et autres détails omis par souci de concision) :

static int sigPipe[2];

static void gotSig ( int num ) { write(sigPipe[1], "!", 1); }

int main ( void ) {
    pipe(sigPipe);
    /* use sigaction to point signal(s) at gotSig() */

    FD_SET(sigPipe[0], &readFDs);

    for (;;) {
        n = select(nFDs, &readFDs, ...);
        if (FD_ISSET(sigPipe[0], &readFDs)) {
            read(sigPipe[0], ch, 1);
            /* do something about the signal here */
        }
        /* ... the rest of your select loop */
    }
}

Si vous vous souciez de quel signal il s'agissait, l'octet le long du tube peut être le numéro du signal.

John Hascall
la source
-1

Vous pouvez utiliser printf dans les gestionnaires de signaux si vous utilisez la bibliothèque pthread. unix / posix spécifie que printf est atomique pour les threads cf Dave Butenhof répondez ici: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Notez que pour avoir une image plus claire de la sortie printf, vous devez exécuter votre application dans une console (sous Linux, utilisez ctl + alt + f1 pour démarrer la console 1), plutôt qu'un pseudo-tty créé par l'interface graphique.

drlolly
la source
3
Les gestionnaires de signaux ne s'exécutent pas dans un thread séparé, ils s'exécutent dans le contexte du thread qui était en cours d'exécution lorsque l'interruption de signal s'est produite. Cette réponse est complètement fausse.
itaych