93 votes

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

Puisque printf n'est pas réentrant, son utilisation dans un gestionnaire de signaux n'est pas sûre. Mais j'ai vu beaucoup d'exemples de codes qui utilisent printf cette façon.

Ma question est donc la suivante: quand devons-nous éviter d'utiliser printf dans un gestionnaire de signal, et existe-t-il un remplaçant recommandé?

62voto

Jonathan Leffler Points 299946

Le principal problème est que si le signal interrompt malloc() ou d'une même fonction, l'état interne peut être temporairement incohérent alors qu'il est de déplacer les blocs de la mémoire entre le libre et utilisé de la liste, ou d'autres opérations similaires. Si le code dans le gestionnaire de signal appelle une fonction qui invoque alors malloc(), ce qui peut complètement détruire la gestion de la mémoire.

La norme C prend une vue très conservatrice de ce que vous pouvez faire dans un gestionnaire de signal:

ISO/IEC 9899:2011 §7.14.1.1 L' signal fonction

¶5 Si le signal se produit que sur le résultat de l'appel de l'abandon ou de soulever la fonction, la le comportement est indéfini si le gestionnaire de signal se réfère à un objet statique ou thread durée de stockage qui n'est pas un lock-atomique sans autre objet que par l'attribution d'une valeur à une objet déclaré en tant que volatile sig_atomic_t, ou le gestionnaire de signal appelle une fonction de la bibliothèque standard, autres que l' abort de la fonction, l' _Exit de la fonction, la quick_exit de la fonction, ou l' signal fonction avec le premier argument égal à la numéro de signal correspondant au signal qui a provoqué l'invocation de la gestionnaire. En outre, si un tel appel à l' signal résultats de la fonction d'un SIG_ERR retour, l' la valeur de errno est indéterminée.252)

252) 252) Si un signal est généré par un asynchrones gestionnaire de signal, le comportement est indéfini.

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

Signal Concepts dit:

Si le processus est multi-thread, ou si le processus est monothread et un gestionnaire de signal est exécutée que sur le résultat de:

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

  • En attente d'un signal non bloqué et être livrés avant l'appel débloqués il retourne

le comportement est indéfini si le gestionnaire de signal se réfère à un objet autre que errno avec statique de la durée de stockage autres que par affectation d'une valeur à un objet déclaré en tant que volatile sig_atomic_t, ou si le gestionnaire de signal appelle une fonction définie dans la présente norme autre que l'une des fonctions énumérées dans le tableau suivant.

Le tableau suivant définit un ensemble de fonctions qui doivent être async un signal fort. Par conséquent, les applications peuvent les invoquer, sans restriction, à partir du signal-la capture de fonctions:

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()
abort()             fstat()             pthread_kill()      sigset()
accept()            fstatat()           pthread_self()      sigsuspend()
access()            fsync()             pthread_sigmask()   sleep()
aio_error()         ftruncate()         raise()             sockatmark()
aio_return()        futimens()          read()              socket()
aio_suspend()       getegid()           readlink()          socketpair()
alarm()             geteuid()           readlinkat()        stat()
bind()              getgid()            recv()              symlink()
cfgetispeed()       getgroups()         recvfrom()          symlinkat()
cfgetospeed()       getpeername()       recvmsg()           tcdrain()
cfsetispeed()       getpgrp()           rename()            tcflow()
cfsetospeed()       getpid()            renameat()          tcflush()
chdir()             getppid()           rmdir()             tcgetattr()
chmod()             getsockname()       select()            tcgetpgrp()
chown()             getsockopt()        sem_post()          tcsendbreak()
clock_gettime()     getuid()            send()              tcsetattr()
close()             kill()              sendmsg()           tcsetpgrp()
connect()           link()              sendto()            time()
creat()             linkat()            setgid()            timer_getoverrun()
dup()               listen()            setpgid()           timer_gettime()
dup2()              lseek()             setsid()            timer_settime()
execl()             lstat()             setsockopt()        times()
execle()            mkdir()             setuid()            umask()
execv()             mkdirat()           shutdown()          uname()
execve()            mkfifo()            sigaction()         unlink()
faccessat()         mkfifoat()          sigaddset()         unlinkat()
fchdir()            mknod()             sigdelset()         utime()
fchmod()            mknodat()           sigemptyset()       utimensat()
fchmodat()          open()              sigfillset()        utimes()
fchown()            openat()            sigismember()       wait()
fchownat()          pause()             signal()            waitpid()
fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

Toutes les fonctions qui ne sont pas dans le tableau ci-dessus sont considérés comme dangereux à l'égard de signaux. En présence de signaux, toutes les fonctions définies par le présent volume de POSIX.1-2008 date doit se comporter comme défini lorsqu'il est appelé à partir de ou interrompu par un signal de la capture de la fonction, avec une seule exception: lorsqu'un signal interrompt un dangereux la fonction et le signal de la capture appels de fonctions dangereuses de la fonction, le comportement est indéfini.

Les opérations d'obtenir la valeur de errno et des opérations qui lui affecter une valeur de errno est asynchrone par un signal fort.

Lorsqu'un signal est transmis à un fil, si l'action de ce signal indique la résiliation, de l'arrêter ou de continuer, l'ensemble du processus doit être dénoncé, arrêté ou poursuivi, respectivement.

Cependant, l' printf() famille de fonctions est absente de cette liste et ne peut pas être appelé en toute sécurité à partir d'un gestionnaire de signal.

En conséquence, vous ne finissent soit à l'aide de write() sans la mise en forme du soutien fourni par printf() et al, ou vous finissez par la définition d'un indicateur de test (périodiquement) dans les endroits appropriés dans votre code. Cette technique est parfaitement incarné dans la réponse par Grijesh Chauhan.

61voto

Grijesh Chauhan Points 28442

Vous pouvez utiliser une variable d'indicateur, jeu de drapeau à l'intérieur gestionnaire de signal, et basé sur le drapeau appel printf() de la fonction dans le main() ou une autre partie du programme en cours de fonctionnement normal.

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

Remarquez dans l'exemple ci-dessous, gestionnaire de signal ding() définir un indicateur alarm_fired à 1 comme SIGALRM pris et dans la fonction principale d' alarm_fired de la valeur est examiné pour conditionnellement appel de printf correctement.

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 Linux Programmation, 4e Édition, Dans ce livre exactement votre code est expliqué (ce que vous voulez), Chapitre 11: les Processus et les Signaux, page 484

En outre, vous devez prendre un soin particulier dans la rédaction des fonctions de gestionnaire, car ils peuvent être appelés de manière asynchrone. C'est, un gestionnaire pourrait être appelé à tout moment dans le programme, de façon imprévisible. Si deux signaux arrivent pendant un intervalle très court, un gestionnaire peut s'exécuter à l'intérieur de l'autre. Et Il est considéré comme une meilleure pratique de déclarer volatile sigatomic_t, ce type sont toujours accessibles de façon atomique, d'éviter toute incertitude à propos d'interrompre l'accès à une variable. (lire: les Données Atomiques d'Accès et de traitement du Signal pour le détail de l'expiation).

Lire la Définition des Gestionnaires de Signaux :apprendre à écrire un gestionnaire de signal fonction qui peut être établi avec l' signal() ou sigaction() fonctions.
Liste des fonctions autorisées dans la page de manuel, l'appel de cette fonction à l'intérieur de gestionnaire de signal est fort.

14voto

alk Points 26509

1 Évitez-le toujours.

2 Au moins sur les systèmes conformes à POSIX, vous pouvez utiliser write(STDOUT_FILENO, ...) au lieu de printf() .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X