2010-12-12 70 views
2

Je suis actuellement en train de mettre en place un programme de problèmes producteurs/consommateurs. J'ai un parent et plusieurs processus d'enfant. Tout fonctionne, mais maintenant j'ai besoin de faire de ma sortie de programme chaque k millisecondes la progression de la tâche que mon programme est en train de faire. Au début, je pensais que ce serait peut-être juste d'utiliser les fonctions signal() et alarm(), mais à partir de quelques tests préliminaires, je l'ai fait ne semble pas suffisant. J'ai regardé plusieurs fichiers journaux et il semble que onAlarm() ne soit pas appelé. Je suppose que cela a à voir avec le fait que tant que les parents et les enfants sont «occupés», ils ne reçoivent pas les événements? Ou même s'ils sont occupés, ils devraient pouvoir recevoir des appels sur onAlarm()? La seule solution que je vois pour cela est de créer un autre processus, qui a comme responsabilité unique traitant de cela.Les fonctions signal() et alarm() fonctionnent-elles même lorsque le processus sur lequel elles sont exécutées est occupé? Ou devrais-je l'exécuter sur un autre processus dédié?

Ceci est mon code « événements »:

void onAlarm() { 
    signal(SIGALRM, onAlarm); 
    alarm(0.01); 

     fprintf(outputFile, "ALAAAAAAAAAAAAAARMMMMMMMMMMM: %d\n", numberOfBytesRead); 
} 

int main() { 
    signal(SIGALRM, onAlarm); 
    alarm(0.01); 
     ... 
} 

Répondre

4

Votre principal problème est que alarm() prend un nombre entier de secondes - et alarm(0) permet d'annuler toutes les alarmes en circulation.

La question de suivi est naturel:

Comment puis-je faire sous-deuxième attente?

Je ne suis pas sûr de la façon approuvée. Sur certains systèmes, il y a un appel micro-sleep (usleep()), mais cela ne fait pas partie de POSIX 2008. L'analogue direct de usleep() dans POSIX semble être nanosleep(). Il y a un sigtimewait() qui pourrait probablement être utilisé pour obtenir l'effet. (Vous pourriez être en mesure d'utiliser setitimer() et getitimer() au lieu de usleep().)

La difficulté avec tous ceux-ci est que vous êtes soit synchrone (vous ne pouvez pas obtenir avec le travail en attendant un signal pour arriver) ou pas envoyé un signal. Je ne vois pas immédiatement un mécanisme d'alarme POSIX sous-seconde, ce qui vous permettrait de continuer à travailler en attendant le délai d'expiration.

+0

Comment puis-je faire accepter 500ms, par exemple? –

+0

Utilisez 'setitimer' avec' ITIMER_REAL'. Recherchez des exemples utilisant cette fonction. – nategoose

1

Si vous avez besoin de sous-deuxième résolution sur une minuterie, vous pouvez utiliser un second fil posix et un usleep

+0

Il y avait un 'ualarm' qui aurait pu le faire, mais POSIX ne l'aime plus. – nategoose

2

Je ne sais pas si c'est votre problème, mais ce n'est pas sûr de fprintf dans un gestionnaire de signal. Par exemple, le signal pourrait être déclenché à l'intérieur de fprintf lui-même, ce qui pourrait entraîner un comportement inattendu. Ou peut-être fprintf alloue de la mémoire, et le signal a été intercepté alors que malloc était en cours d'exécution. Ce genre de chose peut produire des blocages apparemment aléatoires et des accidents violents. La manière sûre d'effectuer des calculs complexes à l'intérieur d'un gestionnaire de signal est que votre gestionnaire de signal modifie l'état d'une boucle d'événement déjà en cours, ou quelque chose comme ça. Ou, pour votre problème spécifique, comme d'autres le suggèrent, évitez d'utiliser des signaux pour cela et utilisez un appel de sommeil plus simple.

+0

les pages 'man' disent souvent si une fonction est sûre dans un gestionnaire de signal et disent parfois quand elle ne l'est pas. Habituellement, les appels système extrêmement simples (pas d'E/S) et les fonctions de calcul sont sécurisés. Si vous savez ce que vous faites, vous pouvez parfois vous en sortir avec les gestionnaires de signaux, mais c'est compliqué. – nategoose

+0

@nategoose - Vous pouvez certainement vous en sortir avec des E/S si cela se fait uniquement via les syscalls, mais appeler 'stdio' est une autre histoire. Il gère les tampons de lecture/écriture en mode utilisateur et comme je l'ai dit il y a quelques années sur ce post, ceux-ci pourraient être un état incohérent de vous obtenez un signal à l'intérieur d'une fonction 'stdio'. Et il pourrait allouer de la mémoire qui n'est pas sûre pour des raisons similaires. – asveikau

+0

@asveikay: "Si vous savez ce que vous faites" englobe beaucoup. 'fprintf (stderr,' peut fonctionner, selon la façon dont il est implémenté, sauf que les appels du gestionnaire de signal peuvent décaler leur sortie au milieu d'une autre sortie.Ceci est généralement tolérable pour la sortie de débogage. Vous pouvez également les utiliser librement si vous bloquez/débloquez des signaux autour de ces fonctions dans le code normal. grands programmes, il devient difficile. – nategoose

0

Pour une temporisation de sous-seconde envoyant un signal, vous devez utiliser la fonction POSIX setitimer (2).

1

La réponse courte à votre question est oui.Ils fonctionnent même si le processus auquel ils sont rattachés est occupé à faire des calculs ou à attendre quelque chose. La seule limitation est que le processus ne change pas et joue avec SIGALRM lui-même ce qui causerait des problèmes. Lorsqu'un signal arrive, l'exécution normale d'un processus/thread est suspendue et le gestionnaire de signal est appelé. C'est l'idée de la gestion des signaux et pourquoi elle est appelée asynchronous.

La réponse la plus longue à votre question est non. Vous ne voudriez pas implémenter de rapport de progression via le mécanisme du gestionnaire de signaux car l'API que vous pouvez utiliser dans un gestionnaire de signal est très limitée. Pour être exact l'exemple que vous avez mentionné mal, car il utilise fprintf(3). Comme indiqué dans l'une des réponses, les signaux sont asynchrones. Cela signifie que si le signal arrive au milieu du code principal appelant, malloc(3), et votre code appelle malloc(3) ainsi (vous ne pouvez jamais savoir, printf(3) peut appeler malloc(3) pour la mise en mémoire tampon et d'autres besoins) alors vous corrompt les données internes des mallocs structures et provoquer le programme de faute. Vous pouvez même avoir des problèmes pour appeler vos propres fonctions qui ne sont pas asynchrones. Vous avez une liste de fonctions sûres que vous pouvez appeler à l'intérieur d'un gestionnaire de signal et vous pouvez les trouver dans man 7 signal sous Async-signal-safe functions. Donc oui, techniquement, vous pouvez implémenter des rapports de progression via alarm(3) tant que vous êtes prêt à vivre avec cette API réduite et que c'est la raison pour laquelle je ne le ferais pas à moins que le programme ne soit unique par conception et à moins qu'il n'y ait vraiment aucun moyen Je vois le code de rapport d'avancement comme étant sujet à de futures améliorations qui rendront difficile d'écrire dans un gestionnaire de signal.

Un autre problème qui a été indiqué à propos de votre exemple est que alarm(2) n'accepte pas les arguments inférieurs à la seconde et l'exemple ci-dessus aurait dû échouer complètement ou afficher au moins quelques avertissements à ce sujet.

Pour la résolution de la microseconde, vous pouvez utiliser setitimer(2) avec ITIMER_REAL comme cela a été indiqué.

Pour la résolution de nanoseconde sur Linux, vous pouvez utiliser timer_create(2), CLOCK_REALTIME, SIGEV_SIGNAL et timer_settime(2) qui ont beaucoup plus de fonctionnalités.

Voici un exemple de code. Notez que cela utilise mes propres macros de gestion des erreurs. Vous pouvez le voir dans un état compilable dans ce projet demos-linux

#include <signal.h> // for signal(2), SIG_ERR 
#include <unistd.h> // for alarm(2), write(2) 
#include <stdlib.h> // for EXIT_SUCCESS 
#include <err_utils.h> // for CHECK_NOT_M1(), CHECK_NOT_SIGT() 
#include <stdio.h> // for snprintf(3), STDERR_FILENO 
/* 
* This is an example of doing progress reports via the SIGALRM signal every second. 
* The main code does a tight calculation loop and the progress reporting to stderr 
* (file descriptor 2) is done via the alarm signal handler. 
*/ 

/* 
* The variables are global to allow the signal handler to access them easily 
* You DONT need to initialize them to 0 since that is the default. 
* The 'volatile' on i is *critical* since it will be accessed asynchronously 
* and the compiler needs to know not to put it in a register since that 
* will mean that we cannot report it's value correctly from the signal 
* handler. 
*/ 
volatile unsigned long i; 
/* 
* Remember that this is a signal handler and calls to fprintf(3) or the like 
* are forbidden so we are forced to use async-safe function (see man 7 signal). 
* That is the reason for the cumbersome code. Hopefully snprintf(3) is safe enough 
* to use. 
*/ 
static void handler(int sig) { 
    // we have to reschedule the SIGALRM every time since the alarm(2) 
    // is a one time deal. 
    CHECK_NOT_SIGT(signal(SIGALRM, handler), SIG_ERR); 
    // no error code from alarm(2) 
    alarm(1); 
    char buf[100]; 
    int len=snprintf(buf, sizeof(buf), "did [%ld] units of work...\n", i); 
    CHECK_NOT_M1(write(STDERR_FILENO, buf, len)); 
} 

int main(int argc, char** argv, char** envp) { 
    CHECK_NOT_SIGT(signal(SIGALRM, handler), SIG_ERR); 
    // no error code from alarm(2) 
    alarm(1); 
    // a very long calculation 
    while(true) { 
     /* Do some real work here */ 
     i++; 
    } 
    return EXIT_SUCCESS; 
}