GNU/Linux >> Tutoriels Linux >  >> Linux

Créer une minuterie sous Linux

La synchronisation de certains événements est une tâche courante pour un développeur. Les scénarios courants pour les temporisateurs sont les chiens de garde, l'exécution cyclique de tâches ou la planification d'événements pour une heure spécifique. Dans cet article, je montre comment créer un minuteur d'intervalle compatible POSIX à l'aide de timer_create(...).

Vous pouvez télécharger le code source des exemples suivants à partir de GitHub.

Préparer Qt Creator

J'ai utilisé Qt Creator comme IDE pour cet exemple. Pour exécuter et déboguer l'exemple de code dans Qt Creator, clonez le référentiel GitHub, ouvrez Qt Creator et accédez à Fichier -> Ouvrir un fichier ou un projet... et choisissez le CMakeLists.txt :

Après avoir sélectionné la chaîne d'outils, cliquez sur Configurer le projet . Le projet contient trois exemples indépendants (nous n'en couvrirons que deux dans cet article). Avec le menu marqué en vert, basculez entre les configurations pour chaque exemple et activez Exécuter dans le terminal pour chacun d'eux (voir la marque jaune ci-dessous). L'exemple actuellement actif pour la construction et le débogage peut être sélectionné sur le Débogage bouton dans le coin inférieur gauche (voir la marque orange ci-dessous) :

Minuteur d'enfilage

Jetons un coup d'œil au simple_threading_timer.c Exemple. C'est la plus simple :elle montre comment un compteur d'intervalles est créé, qui appelle la fonction expired à l'expiration. A chaque expiration, un nouveau thread est créé dans lequel la fonction expiration est appelé.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void expired(union sigval timer_data);

pid_t gettid(void);

struct t_eventData{
    int myData;
};

int main()
{
    int res = 0;
    timer_t timerId = 0;

    struct t_eventData eventData = { .myData = 0 };


    /*  sigevent specifies behaviour on expiration  */
    struct sigevent sev = { 0 };

    /* specify start delay and interval
     * it_value and it_interval must not be zero */

    struct itimerspec its = {   .it_value.tv_sec  = 1,
                                .it_value.tv_nsec = 0,
                                .it_interval.tv_sec  = 1,
                                .it_interval.tv_nsec = 0
                            };

    printf("Simple Threading Timer - thread-id: %d\n", gettid());

    sev.sigev_notify = SIGEV_THREAD;
    sev.sigev_notify_function = &expired;
    sev.sigev_value.sival_ptr = &eventData;


    /* create timer */
    res = timer_create(CLOCK_REALTIME, &sev, &timerId);


    if (res != 0){
        fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
        exit(-1);
    }

    /* start timer */
    res = timer_settime(timerId, 0, &its, NULL);

    if (res != 0){
        fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
        exit(-1);
    }

    printf("Press ETNER Key to Exit\n");
    while(getchar()!='\n'){}
    return 0;
}


void expired(union sigval timer_data){
    struct t_eventData *data = timer_data.sival_ptr;
    printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}

L'avantage de cette approche est son faible encombrement, en termes de code et de débogage simple. L'inconvénient est la surcharge supplémentaire due à la création d'un nouveau thread à l'expiration et, par conséquent, le comportement moins déterministe.

Plus de ressources Linux

  • Aide-mémoire des commandes Linux
  • Aide-mémoire des commandes Linux avancées
  • Cours en ligne gratuit :Présentation technique de RHEL
  • Aide-mémoire sur le réseau Linux
  • Aide-mémoire SELinux
  • Aide-mémoire sur les commandes courantes de Linux
  • Que sont les conteneurs Linux ?
  • Nos derniers articles Linux

Minuteur de signal d'interruption

Une autre possibilité d'être averti par un temporisateur expiré est basée sur un signal du noyau. Au lieu de créer un nouveau thread chaque fois que le temporisateur expire, le noyau envoie un signal au processus, le processus est interrompu et le gestionnaire de signal correspondant est appelé.

Comme l'action par défaut lors de la réception d'un signal est de terminer le processus (voir la page de manuel du signal), nous devons préparer Qt Creator à l'avance afin qu'un débogage correct soit possible.

Le comportement par défaut de Qt Creator lorsque le débogué reçoit un signal est :

  • Interrompre l'exécution et basculer vers le contexte du débogueur.
  • Afficher une fenêtre contextuelle qui informe l'utilisateur de la réception d'un signal.

Les deux actions ne sont pas souhaitées car la réception d'un signal fait partie de notre application.

Qt Creator utilise GDB en arrière-plan. Afin d'empêcher GDB d'arrêter l'exécution lorsque le processus reçoit un signal, allez dans Outils -> Options , sélectionnez Débogueur , et accédez à Locals &Expressions . Ajoutez l'expression suivante à Debugging Helper Customization :

handle SIG34 nostop pass

Vous pouvez trouver plus d'informations sur la gestion des signaux GDB dans la documentation GDB.

Ensuite, nous voulons supprimer la fenêtre contextuelle qui nous avertit à chaque fois qu'un signal est reçu lorsque nous nous arrêtons dans le gestionnaire de signal :

Pour ce faire, accédez à l'onglet GDB et décochez la case cochée :

Vous pouvez maintenant déboguer correctement le signal_interrupt_timer . L'implémentation réelle du temporisateur de signal est un peu plus complexe :

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define UNUSED(x) (void)(x)

static void handler(int sig, siginfo_t *si, void *uc);
pid_t gettid(void);

struct t_eventData{
    int myData;
};

int main()
{
    int res = 0;
    timer_t timerId = 0;


    struct sigevent sev = { 0 };
    struct t_eventData eventData = { .myData = 0 };

    /* specifies the action when receiving a signal */
    struct sigaction sa = { 0 };

    /* specify start delay and interval */
    struct itimerspec its = {   .it_value.tv_sec  = 1,
                                .it_value.tv_nsec = 0,
                                .it_interval.tv_sec  = 1,
                                .it_interval.tv_nsec = 0
                            };

    printf("Signal Interrupt Timer - thread-id: %d\n", gettid());

    sev.sigev_notify = SIGEV_SIGNAL; // Linux-specific
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = &eventData;

    /* create timer */
    res = timer_create(CLOCK_REALTIME, &sev, &timerId);

    if ( res != 0){
        fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
        exit(-1);
    }

    /* specifz signal and handler */
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;

    /* Initialize signal */
    sigemptyset(&sa.sa_mask);

    printf("Establishing handler for signal %d\n", SIGRTMIN);

    /* Register signal handler */
    if (sigaction(SIGRTMIN, &sa, NULL) == -1){
        fprintf(stderr, "Error sigaction: %s\n", strerror(errno));
        exit(-1);
    }

    /* start timer */
    res = timer_settime(timerId, 0, &its, NULL);

    if ( res != 0){
        fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
        exit(-1);
    }

    printf("Press ENTER to Exit\n");
    while(getchar()!='\n'){}
    return 0;
}



static void
handler(int sig, siginfo_t *si, void *uc)
{
    UNUSED(sig);
    UNUSED(uc);
    struct t_eventData *data = (struct t_eventData *) si->_sifields._rt.si_sigval.sival_ptr;
    printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}

Contrairement au timer de threading, nous devons initialiser le signal et enregistrer un gestionnaire de signal. Cette approche est plus performante car elle n'entraînera pas la création de threads supplémentaires. Pour cette raison, l'exécution du gestionnaire de signal est également plus déterministe. L'inconvénient est clairement l'effort de configuration supplémentaire pour déboguer cela correctement.

Résumé

Les deux méthodes décrites dans cet article sont des implémentations proches du noyau des minuteries. Même si la fonction timer_create(...) fait partie de la spécification POSIX, il n'est pas possible de compiler l'exemple de code sur un système FreeBSD en raison de petites différences dans les structures de données. Outre cet inconvénient, une telle implémentation vous offre un contrôle précis pour les applications de chronométrage à usage général.


Linux
  1. Créer un coffre-fort de fichiers chiffré sous Linux

  2. Comment créer un script d'une commande Linux

  3. Comment créer un package RPM Linux

  4. Commande Linux ln

  5. Changer la minuterie du noyau Linux

Comment créer des raccourcis sur le bureau Linux

Comment créer un alias SSH sous Linux

Comment créer un alias sous Linux

4 façons de créer un nouveau fichier sous Linux

La commande timer sous Linux

Créer une partition sous Linux - Un guide étape par étape