Timers et sommeil sous Linux

Ce forum est dédié à apprendre le développement de programmes user mode sur Linux

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Timers et sommeil sous Linux

Post by Hydraxx »

Chapitre 23 — Timers et sommeil sous Linux

Objectif du chapitre

Ce chapitre explique comment un programme Linux peut gérer le temps.
On va voir comment faire dormir un processus, comment déclencher une action dans le futur, comment créer des timers périodiques, comment mesurer précisément une durée, et comment utiliser des timers modernes intégrables avec des descripteurs de fichiers.

En programmation système, ce chapitre est très important parce que beaucoup de programmes ont besoin de gérer des délais :
  • attendre quelques secondes ;
  • mettre un timeout sur une opération bloquante ;
  • exécuter une tâche toutes les X secondes ;
  • mesurer le temps écoulé ;
  • déclencher un signal après un délai ;
  • intégrer un timer dans une boucle poll(), select() ou epoll().
1. Vue d’ensemble des familles d’API

Sous Linux, il existe plusieurs familles d’API pour gérer le temps.
Elles ne servent pas toutes exactement au même usage.

Les API historiques UNIX
  • alarm() : programme un signal SIGALRM après un nombre de secondes.
  • setitimer() : crée un timer d’intervalle plus complet que alarm().
  • getitimer() : récupère l’état actuel d’un timer créé avec setitimer().
Les API de sommeil
  • sleep() : suspend l’exécution pendant un nombre de secondes.
  • nanosleep() : version plus précise, avec secondes et nanosecondes.
  • clock_nanosleep() : version avancée qui permet de choisir l’horloge utilisée.
Les API POSIX modernes
  • clock_gettime() : lire une horloge.
  • clock_getres() : obtenir la résolution d’une horloge.
  • clock_settime() : modifier certaines horloges.
  • timer_create() : créer un timer POSIX.
  • timer_settime() : armer ou désarmer un timer POSIX.
  • timer_gettime() : récupérer l’état d’un timer POSIX.
  • timer_delete() : supprimer un timer POSIX.
  • timer_getoverrun() : récupérer le nombre d’expirations non traitées.
L’API Linux moderne
  • timerfd_create() : créer un timer représenté par un descripteur de fichier.
  • timerfd_settime() : configurer ce timer.
  • timerfd_gettime() : lire la configuration restante.
  • read() : lire le nombre d’expirations.
À retenir rapidement

Code: Select all

sleep()              -> dormir simplement
nanosleep()          -> dormir plus précisément
alarm()              -> timer simple avec SIGALRM
setitimer()          -> timer UNIX classique
clock_gettime()      -> mesurer/lire le temps
timer_create()       -> timer POSIX moderne
timerfd_create()     -> timer Linux sous forme de fd
2. La notion de timer

Un timer est un mécanisme qui expire après un certain délai.
Quand le timer expire, le processus peut être notifié de plusieurs manières :
  • par un signal ;
  • par l’appel d’une fonction dans un thread ;
  • par la lecture d’un descripteur de fichier ;
  • ou parfois simplement par une valeur de retour.
Il existe deux grands types de timers.

Timer one-shot

Un timer one-shot expire une seule fois.

Exemple :

Code: Select all

Dans 5 secondes, envoie SIGALRM.
Timer périodique

Un timer périodique expire une première fois, puis recommence régulièrement.

Exemple :

Code: Select all

Première expiration dans 5 secondes,
puis expiration toutes les 2 secondes.
Dans beaucoup d’API Linux, on retrouve cette logique avec deux champs :
  • it_value : délai avant la première expiration ;
  • it_interval : délai entre les expirations suivantes.
Si it_interval vaut zéro, le timer ne se répète pas.

3. alarm() — timer simple avec SIGALRM

Prototype

Code: Select all

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
La fonction alarm() programme l’envoi du signal SIGALRM après un certain nombre de secondes.

Exemple :

Code: Select all

alarm(5);
Cela signifie : dans 5 secondes, le processus recevra SIGALRM.

Annuler une alarme

Code: Select all

alarm(0);
Cette instruction annule l’alarme en cours.

Valeur de retour

alarm() retourne le nombre de secondes restantes de l’ancienne alarme.
Si aucune alarme n’était active, elle retourne 0.

Exemple :

Code: Select all

unsigned int old = alarm(10);
Si une ancienne alarme devait expirer dans 3 secondes, old vaudra 3.

Limitation importante

Il n’y a qu’une seule alarme alarm() active par processus.
Un nouvel appel à alarm() remplace l’ancien.

Exemple minimal

Code: Select all

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void handler(int sig)
{
    write(1, "SIGALRM recu\n", 13);
}

int main(void)
{
    struct sigaction sa = {0};
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);

    sigaction(SIGALRM, &sa, NULL);

    alarm(3);

    while (1)
        pause();

    return 0;
}
Explication
  • On installe un handler pour SIGALRM.
  • On appelle alarm(3).
  • Après 3 secondes, le processus reçoit SIGALRM.
  • Le handler est exécuté.
4. setitimer() — timers UNIX classiques

setitimer() est plus puissant que alarm().
Il permet de créer un timer one-shot ou périodique.

Prototype

Code: Select all

#include <sys/time.h>

int setitimer(int which,
              const struct itimerval *new_value,
              struct itimerval *old_value);
Structure utilisée

Code: Select all

struct itimerval {
    struct timeval it_interval;  // intervalle pour timer periodique
    struct timeval it_value;     // temps avant prochaine expiration
};

struct timeval {
    time_t      tv_sec;   // secondes
    suseconds_t tv_usec;  // microsecondes
};
Rôle des champs
  • it_value : indique dans combien de temps le timer expire.
  • it_interval : indique tous les combien il se répète.
Si it_value vaut zéro, le timer est désactivé.
Si it_interval vaut zéro, le timer expire une seule fois.

Les différents timers

Code: Select all

ITIMER_REAL
ITIMER_VIRTUAL
ITIMER_PROF
ITIMER_REAL

Compte le temps réel, comme une horloge normale.
Quand il expire, il envoie SIGALRM.

ITIMER_VIRTUAL

Compte seulement le temps CPU consommé par le processus en mode utilisateur.
Quand il expire, il envoie SIGVTALRM.

ITIMER_PROF

Compte le temps CPU consommé en mode utilisateur + noyau.
Quand il expire, il envoie SIGPROF.

Exemple avec ITIMER_REAL

Code: Select all

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

void handler(int sig)
{
    write(1, "tick\n", 5);
}

int main(void)
{
    struct sigaction sa = {0};
    struct itimerval timer = {0};

    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL);

    timer.it_value.tv_sec = 2;      // premiere expiration dans 2 sec
    timer.it_value.tv_usec = 0;

    timer.it_interval.tv_sec = 1;   // ensuite toutes les 1 sec
    timer.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &timer, NULL);

    while (1)
        pause();

    return 0;
}
Résultat

Le programme affiche tick après 2 secondes, puis toutes les secondes.

5. getitimer() — lire l’état d’un timer UNIX

Prototype

Code: Select all

#include <sys/time.h>

int getitimer(int which, struct itimerval *curr_value);
Cette fonction permet de récupérer l’état courant d’un timer créé avec setitimer().

Exemple :

Code: Select all

struct itimerval curr;
getitimer(ITIMER_REAL, &curr);
Ensuite :
  • curr.it_value contient le temps restant avant la prochaine expiration.
  • curr.it_interval contient l’intervalle périodique.
6. sleep() — dormir simplement

Prototype

Code: Select all

#include <unistd.h>

unsigned int sleep(unsigned int seconds);
sleep() suspend le thread appelant pendant un nombre de secondes.

Exemple :

Code: Select all

sleep(5);
Le programme dort pendant 5 secondes.

Attention

sleep() peut être interrompu par un signal.
Dans ce cas, il retourne le nombre de secondes restantes.

Exemple :

Code: Select all

unsigned int reste = sleep(10);
Si un signal interrompt le sommeil après 3 secondes, reste peut valoir environ 7.

7. nanosleep() — sommeil plus précis

Prototype

Code: Select all

#include <time.h>

int nanosleep(const struct timespec *req,
              struct timespec *rem);
Structure timespec

Code: Select all

struct timespec {
    time_t tv_sec;  // secondes
    long   tv_nsec; // nanosecondes
};
nanosleep() permet de demander un sommeil plus précis que sleep().

Exemple :

Code: Select all

#include <time.h>

int main(void)
{
    struct timespec ts;

    ts.tv_sec = 1;
    ts.tv_nsec = 500000000; // 0.5 seconde

    nanosleep(&ts, NULL);

    return 0;
}
Ici, le programme dort pendant environ 1,5 seconde.

Gestion de l’interruption

Si un signal interrompt nanosleep(), la fonction retourne -1 et errno vaut EINTR.
Le temps restant peut être récupéré dans rem.

Exemple :

Code: Select all

#include <stdio.h>
#include <errno.h>
#include <time.h>

int main(void)
{
    struct timespec req;
    struct timespec rem;

    req.tv_sec = 10;
    req.tv_nsec = 0;

    while (nanosleep(&req, &rem) == -1 && errno == EINTR) {
        req = rem;
    }

    return 0;
}
Idée importante

Ce code redort automatiquement le temps restant si un signal interrompt le sommeil.

8. Les horloges POSIX

Linux fournit plusieurs horloges.
Elles ne représentent pas toutes la même chose.

APIs principales

Code: Select all

#include <time.h>

int clock_gettime(clockid_t clockid, struct timespec *tp);
int clock_getres(clockid_t clockid, struct timespec *res);
int clock_settime(clockid_t clockid, const struct timespec *tp);
clock_gettime()

Permet de lire la valeur actuelle d’une horloge.

clock_getres()

Permet de connaître la résolution d’une horloge.

clock_settime()

Permet de modifier certaines horloges, par exemple CLOCK_REALTIME.

9. Les principales horloges

CLOCK_REALTIME

Horloge temps réel du système.
Elle correspond à l’heure du système.
Elle peut changer si l’utilisateur ou NTP modifie l’heure.

Exemple :

Code: Select all

clock_gettime(CLOCK_REALTIME, &ts);
Piège

Pour mesurer une durée, CLOCK_REALTIME n’est pas idéale, car l’heure système peut être modifiée.

CLOCK_MONOTONIC

Horloge monotone.
Elle avance toujours dans le même sens.
Elle est très utile pour mesurer des durées.

Exemple :

Code: Select all

clock_gettime(CLOCK_MONOTONIC, &ts);
Usage recommandé

Pour mesurer un temps écoulé, il vaut mieux utiliser CLOCK_MONOTONIC.

CLOCK_PROCESS_CPUTIME_ID

Mesure le temps CPU consommé par tout le processus.

CLOCK_THREAD_CPUTIME_ID

Mesure le temps CPU consommé par un thread précis.

Résumé

Code: Select all

CLOCK_REALTIME            -> heure système, modifiable
CLOCK_MONOTONIC           -> horloge monotone, idéale pour mesurer une durée
CLOCK_PROCESS_CPUTIME_ID  -> temps CPU du processus
CLOCK_THREAD_CPUTIME_ID   -> temps CPU du thread
10. Mesurer une durée avec clock_gettime()

Exemple propre pour mesurer une durée :

Code: Select all

#include <stdio.h>
#include <time.h>
#include <unistd.h>

static double diff_seconds(struct timespec start, struct timespec end)
{
    return (end.tv_sec - start.tv_sec) +
           (end.tv_nsec - start.tv_nsec) / 1000000000.0;
}

int main(void)
{
    struct timespec start;
    struct timespec end;

    clock_gettime(CLOCK_MONOTONIC, &start);

    sleep(2);

    clock_gettime(CLOCK_MONOTONIC, &end);

    printf("Temps ecoule : %.3f secondes\n", diff_seconds(start, end));

    return 0;
}
Pourquoi CLOCK_MONOTONIC ?

Parce qu’on mesure une durée.
Si l’heure système change pendant le programme, CLOCK_MONOTONIC reste cohérente.

11. clock_nanosleep() — dormir avec une horloge choisie

Prototype

Code: Select all

#include <time.h>

int clock_nanosleep(clockid_t clockid,
                    int flags,
                    const struct timespec *request,
                    struct timespec *remain);
clock_nanosleep() ressemble à nanosleep(), mais permet de choisir l’horloge.

Exemple :

Code: Select all

struct timespec ts;
ts.tv_sec = 2;
ts.tv_nsec = 0;

clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
Le flag TIMER_ABSTIME

Avec TIMER_ABSTIME, le temps demandé est une date absolue sur l’horloge choisie.

Exemple :

Code: Select all

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL);
Cela signifie : dormir jusqu’à ce que CLOCK_MONOTONIC atteigne la valeur deadline.

Avantage

Très utile pour éviter la dérive dans une boucle périodique.

Exemple de boucle périodique propre

Code: Select all

#include <stdio.h>
#include <time.h>

static void add_ms(struct timespec *ts, long ms)
{
    ts->tv_nsec += ms * 1000000L;

    while (ts->tv_nsec >= 1000000000L) {
        ts->tv_sec++;
        ts->tv_nsec -= 1000000000L;
    }
}

int main(void)
{
    struct timespec next;

    clock_gettime(CLOCK_MONOTONIC, &next);

    for (int i = 0; i < 10; i++) {
        add_ms(&next, 1000); // prochaine execution dans 1 seconde

        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);

        printf("tick %d\n", i);
    }

    return 0;
}
Pourquoi c’est mieux qu’un simple sleep(1) ?

Parce que si ton traitement prend du temps, sleep(1) ajoute ce temps à chaque tour.
Avec TIMER_ABSTIME, tu vises une date précise, donc tu limites la dérive.

12. POSIX timers — timers modernes

Les timers POSIX sont plus puissants que alarm() et setitimer().
Ils permettent notamment :
  • de créer plusieurs timers dans un même processus ;
  • de choisir l’horloge utilisée ;
  • de choisir la méthode de notification ;
  • de récupérer les overruns ;
  • de faire des timers one-shot ou périodiques.
APIs principales

Code: Select all

timer_create()
timer_settime()
timer_gettime()
timer_delete()
timer_getoverrun()
13. timer_create() — créer un timer POSIX

Prototype

Code: Select all

#include <signal.h>
#include <time.h>

int timer_create(clockid_t clockid,
                 struct sigevent *sevp,
                 timer_t *timerid);
Paramètres
  • clockid : horloge utilisée par le timer.
  • sevp : méthode de notification.
  • timerid : identifiant du timer créé.
Exemple :

Code: Select all

timer_t tid;
timer_create(CLOCK_MONOTONIC, NULL, &tid);
Si sevp vaut NULL, Linux utilise une notification par défaut, généralement via signal.

14. struct sigevent — choisir la notification

Structure importante :

Code: Select all

struct sigevent {
    int sigev_notify;
    int sigev_signo;
    union sigval sigev_value;
    void (*sigev_notify_function)(union sigval);
    pthread_attr_t *sigev_notify_attributes;
};
Modes principaux

Code: Select all

SIGEV_NONE
SIGEV_SIGNAL
SIGEV_THREAD
SIGEV_NONE

Le timer expire, mais aucune notification n’est envoyée.

SIGEV_SIGNAL

Le timer envoie un signal au processus quand il expire.

SIGEV_THREAD

Le timer appelle une fonction dans un thread séparé.

15. timer_settime() — armer ou désarmer un timer

Prototype

Code: Select all

#include <time.h>

int timer_settime(timer_t timerid,
                  int flags,
                  const struct itimerspec *new_value,
                  struct itimerspec *old_value);
Structure itimerspec

Code: Select all

struct itimerspec {
    struct timespec it_interval; // repetition
    struct timespec it_value;    // premiere expiration
};
Exemple de configuration

Code: Select all

struct itimerspec its;

its.it_value.tv_sec = 5;
its.it_value.tv_nsec = 0;

its.it_interval.tv_sec = 2;
its.it_interval.tv_nsec = 0;

timer_settime(tid, 0, &its, NULL);
Cela signifie :
  • première expiration dans 5 secondes ;
  • ensuite expiration toutes les 2 secondes.
Désarmer un timer

Pour désarmer un timer, on met it_value à zéro.

Code: Select all

struct itimerspec its = {0};
timer_settime(tid, 0, &its, NULL);
16. timer_gettime() — lire l’état d’un timer POSIX

Prototype

Code: Select all

#include <time.h>

int timer_gettime(timer_t timerid,
                  struct itimerspec *curr_value);
Cette fonction récupère :
  • le temps restant avant la prochaine expiration ;
  • l’intervalle périodique du timer.
Exemple :

Code: Select all

struct itimerspec curr;
timer_gettime(tid, &curr);
17. timer_delete() — supprimer un timer

Prototype

Code: Select all

#include <time.h>

int timer_delete(timer_t timerid);
Chaque timer POSIX consomme des ressources noyau.
Quand on a fini de l’utiliser, il faut le supprimer.

Code: Select all

timer_delete(tid);
Note

Quand un processus se termine, ses timers sont supprimés automatiquement.
Mais dans un programme propre, on libère quand même les ressources explicitement.

18. Notification par signal avec timer_create()

Exemple complet avec SIGEV_SIGNAL.

Code: Select all

#define _POSIX_C_SOURCE 199309L

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

static void handler(int sig, siginfo_t *si, void *uc)
{
    timer_t *tidptr = si->si_value.sival_ptr;

    (void)uc;
    (void)tidptr;

    write(1, "timer expire\n", 13);
}

int main(void)
{
    struct sigaction sa = {0};
    struct sigevent sev = {0};
    struct itimerspec its = {0};
    timer_t tid;

    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = &tid;

    if (timer_create(CLOCK_MONOTONIC, &sev, &tid) == -1) {
        perror("timer_create");
        exit(EXIT_FAILURE);
    }

    its.it_value.tv_sec = 2;
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 1;
    its.it_interval.tv_nsec = 0;

    if (timer_settime(tid, 0, &its, NULL) == -1) {
        perror("timer_settime");
        exit(EXIT_FAILURE);
    }

    while (1)
        pause();

    return 0;
}
Explication
  • On installe un handler avec sigaction().
  • On demande au timer d’envoyer SIGRTMIN.
  • Le timer expire une première fois après 2 secondes.
  • Ensuite il expire toutes les secondes.
Attention aux handlers

Dans un handler de signal, il faut éviter les fonctions non async-signal-safe.
Par exemple, printf() est déconseillé dans un vrai handler.
On préfère write() pour un exemple minimal.

19. timer_getoverrun() — les expirations ratées

Un overrun arrive quand un timer expire plusieurs fois avant que le programme traite la notification.

Exemple :

Code: Select all

Timer toutes les 1 seconde.
Programme bloqué pendant 5 secondes.
Quand il revient, plusieurs expirations se sont accumulées.
Prototype

Code: Select all

#include <time.h>

int timer_getoverrun(timer_t timerid);
Cette fonction retourne le nombre d’expirations supplémentaires non traitées.

À quoi ça sert ?

Ça sert surtout quand les timers sont rapides ou quand le programme peut être bloqué.

20. Notification par thread avec SIGEV_THREAD

Avec SIGEV_THREAD, le timer appelle directement une fonction dans un thread séparé.

Exemple simplifié

Code: Select all

#define _POSIX_C_SOURCE 199309L

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

static void timer_func(union sigval sv)
{
    int *value = sv.sival_ptr;
    printf("Timer expire, valeur = %d\n", *value);
}

int main(void)
{
    struct sigevent sev = {0};
    struct itimerspec its = {0};
    timer_t tid;
    int data = 1234;

    sev.sigev_notify = SIGEV_THREAD;
    sev.sigev_notify_function = timer_func;
    sev.sigev_value.sival_ptr = &data;

    if (timer_create(CLOCK_MONOTONIC, &sev, &tid) == -1) {
        perror("timer_create");
        exit(EXIT_FAILURE);
    }

    its.it_value.tv_sec = 2;
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 1;
    its.it_interval.tv_nsec = 0;

    if (timer_settime(tid, 0, &its, NULL) == -1) {
        perror("timer_settime");
        exit(EXIT_FAILURE);
    }

    sleep(10);

    timer_delete(tid);
    return 0;
}
Avantage

Avec SIGEV_THREAD, la fonction appelée n’est pas un handler de signal classique.
Elle est appelée dans un thread, donc les contraintes sont moins strictes.

Attention

Comme il y a des threads, il faut faire attention aux accès concurrents aux variables partagées.
Si plusieurs threads modifient les mêmes données, il faut utiliser mutex, atomic, etc.

21. timerfd — timers sous forme de descripteur de fichier

timerfd est une API spécifique Linux.
Elle permet de créer un timer qui se comporte comme un descripteur de fichier.

C’est extrêmement pratique dans les programmes système modernes.

Pourquoi c’est intéressant ?

Parce qu’on peut intégrer le timer dans :

Code: Select all

select()
poll()
epoll()
Donc dans une event loop, on peut attendre en même temps :
  • des sockets réseau ;
  • des fichiers ;
  • des pipes ;
  • des signaux via signalfd ;
  • des timers via timerfd.
22. timerfd_create()

Prototype

Code: Select all

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);
Exemple :

Code: Select all

int fd = timerfd_create(CLOCK_MONOTONIC, 0);
Flags utiles

Code: Select all

TFD_CLOEXEC
TFD_NONBLOCK
TFD_CLOEXEC

Ferme automatiquement le fd lors d’un execve().

TFD_NONBLOCK

Met le fd en mode non bloquant.

23. timerfd_settime()

Prototype

Code: Select all

#include <sys/timerfd.h>

int timerfd_settime(int fd,
                    int flags,
                    const struct itimerspec *new_value,
                    struct itimerspec *old_value);
Même structure que les timers POSIX :

Code: Select all

struct itimerspec {
    struct timespec it_interval;
    struct timespec it_value;
};
Exemple

Code: Select all

struct itimerspec its = {0};

its.it_value.tv_sec = 1;
its.it_interval.tv_sec = 1;

timerfd_settime(fd, 0, &its, NULL);
Cela crée un timer qui expire dans 1 seconde, puis toutes les secondes.

24. Lire un timerfd avec read()

Quand le timer expire, on peut lire sur le fd.

Code: Select all

uint64_t expirations;
read(fd, &expirations, sizeof(expirations));
La variable expirations contient le nombre d’expirations depuis la dernière lecture.

Exemple

Si le timer expire toutes les secondes et que le programme ne lit pas pendant 5 secondes, alors read() peut récupérer :

Code: Select all

expirations = 5
C’est très pratique, car on sait combien d’expirations se sont accumulées.

25. Exemple complet avec timerfd

Code: Select all

#define _GNU_SOURCE

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <time.h>

int main(void)
{
    int fd;
    struct itimerspec its = {0};
    uint64_t expirations;

    fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    if (fd == -1) {
        perror("timerfd_create");
        exit(EXIT_FAILURE);
    }

    its.it_value.tv_sec = 1;      // premiere expiration dans 1 sec
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 1;   // repetition toutes les 1 sec
    its.it_interval.tv_nsec = 0;

    if (timerfd_settime(fd, 0, &its, NULL) == -1) {
        perror("timerfd_settime");
        close(fd);
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < 5; i++) {
        ssize_t n = read(fd, &expirations, sizeof(expirations));
        if (n != sizeof(expirations)) {
            perror("read");
            close(fd);
            exit(EXIT_FAILURE);
        }

        printf("expiration(s) lue(s) : %llu\n",
               (unsigned long long)expirations);
    }

    close(fd);
    return 0;
}
Compilation

Code: Select all

gcc timerfd_demo.c -o timerfd_demo
Résultat attendu

Code: Select all

expiration(s) lue(s) : 1
expiration(s) lue(s) : 1
expiration(s) lue(s) : 1
expiration(s) lue(s) : 1
expiration(s) lue(s) : 1
26. Exemple timerfd avec poll()

Voici un exemple qui montre pourquoi timerfd est pratique.
On peut attendre un timer avec poll().

Code: Select all

#define _GNU_SOURCE

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/timerfd.h>
#include <time.h>

int main(void)
{
    int fd;
    struct itimerspec its = {0};
    struct pollfd pfd;
    uint64_t expirations;

    fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    if (fd == -1) {
        perror("timerfd_create");
        exit(EXIT_FAILURE);
    }

    its.it_value.tv_sec = 2;
    its.it_interval.tv_sec = 2;

    if (timerfd_settime(fd, 0, &its, NULL) == -1) {
        perror("timerfd_settime");
        close(fd);
        exit(EXIT_FAILURE);
    }

    pfd.fd = fd;
    pfd.events = POLLIN;

    while (1) {
        int ret = poll(&pfd, 1, -1);
        if (ret == -1) {
            perror("poll");
            break;
        }

        if (pfd.revents & POLLIN) {
            if (read(fd, &expirations, sizeof(expirations)) != sizeof(expirations)) {
                perror("read");
                break;
            }

            printf("Timer expire : %llu fois\n",
                   (unsigned long long)expirations);
        }
    }

    close(fd);
    return 0;
}
Intérêt

Dans un vrai serveur, on peut surveiller plusieurs choses dans le même poll() :
  • une socket client ;
  • une socket serveur ;
  • un pipe ;
  • un timerfd ;
  • etc.
27. Timeout sur une opération bloquante

Un usage classique des timers est d’éviter qu’un programme reste bloqué trop longtemps.

Exemple : on veut lire depuis stdin, mais seulement pendant 5 secondes.

Ancienne méthode possible : utiliser alarm().

Exemple avec alarm()

Code: Select all

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

static void handler(int sig)
{
    (void)sig;
}

int main(void)
{
    char buf[128];
    struct sigaction sa = {0};

    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL);

    alarm(5);

    ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);

    alarm(0);

    if (n == -1) {
        if (errno == EINTR)
            printf("Timeout : read interrompu\n");
        else
            perror("read");
    } else {
        buf[n] = '\0';
        printf("Lu : %s\n", buf);
    }

    return 0;
}
Limite

Cette technique fonctionne, mais elle est ancienne et basée sur les signaux.
Dans un programme moderne, poll(), select(), epoll() ou timerfd sont souvent plus propres.

28. Différence entre temps réel et temps CPU

Il faut bien distinguer :

Temps réel

C’est le temps qui passe dans le monde réel.

Exemple :

Code: Select all

sleep(5);
Ici, environ 5 secondes réelles passent.

Temps CPU

C’est le temps pendant lequel le processus utilise réellement le processeur.

Exemple :

Code: Select all

CLOCK_PROCESS_CPUTIME_ID
CLOCK_THREAD_CPUTIME_ID
Si un programme dort pendant 5 secondes, il consomme presque zéro temps CPU.

Exemple mental

Code: Select all

sleep(10);
Temps réel : environ 10 secondes.
Temps CPU : presque 0 seconde.

29. Résolution et précision des timers

La résolution d’une horloge indique la plus petite unité de temps qu’elle peut représenter ou mesurer.

On peut la lire avec :

Code: Select all

clock_getres()
Exemple :

Code: Select all

#include <stdio.h>
#include <time.h>

int main(void)
{
    struct timespec res;

    clock_getres(CLOCK_MONOTONIC, &res);

    printf("Resolution : %ld sec, %ld nsec\n",
           res.tv_sec, res.tv_nsec);

    return 0;
}
Attention

Demander un sommeil de 1 nanoseconde ne veut pas dire que le programme va réellement reprendre exactement 1 nanoseconde plus tard.
Le scheduler, la charge CPU, les interruptions et la résolution réelle du système influencent le réveil.

30. Ce qu’il faut éviter

Utiliser CLOCK_REALTIME pour mesurer une durée

Mauvais choix :

Code: Select all

clock_gettime(CLOCK_REALTIME, &start);
Pour mesurer un délai, il faut préférer :

Code: Select all

clock_gettime(CLOCK_MONOTONIC, &start);
Utiliser printf() dans un handler de signal

Dans un handler :

Code: Select all

printf("signal\n"); // a eviter
Préférer :

Code: Select all

write(1, "signal\n", 7);
Oublier de gérer EINTR

Beaucoup d’appels bloquants peuvent être interrompus par un signal.
Il faut vérifier errno == EINTR.

Oublier timer_delete()

Quand un timer POSIX n’est plus utile :

Code: Select all

timer_delete(tid);
31. Tableau récapitulatif des APIs

Code: Select all

API                     Role
------------------------------------------------------------
alarm()                 Timer simple avec SIGALRM
setitimer()             Timer UNIX one-shot ou periodique
getitimer()             Lire l'etat d'un timer setitimer
sleep()                 Dormir un nombre de secondes
nanosleep()             Dormir avec precision nanoseconde
clock_gettime()         Lire une horloge
clock_getres()          Lire la resolution d'une horloge
clock_settime()         Modifier une horloge si autorise
clock_nanosleep()       Dormir avec une horloge choisie
timer_create()          Creer un timer POSIX
timer_settime()         Armer/desarmer un timer POSIX
timer_gettime()         Lire l'etat d'un timer POSIX
timer_delete()          Supprimer un timer POSIX
timer_getoverrun()      Lire les expirations ratees
timerfd_create()        Creer un timer sous forme de fd
timerfd_settime()       Armer/desarmer un timerfd
timerfd_gettime()       Lire l'etat d'un timerfd
read()                  Lire les expirations d'un timerfd
32. Quel choix utiliser en pratique ?

Pour dormir simplement

Code: Select all

sleep()
Pour dormir plus précisément

Code: Select all

nanosleep()
Pour une boucle périodique propre

Code: Select all

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)
Pour mesurer une durée

Code: Select all

clock_gettime(CLOCK_MONOTONIC, ...)
Pour un timer POSIX portable

Code: Select all

timer_create()
timer_settime()
Pour un programme Linux moderne avec poll/epoll

Code: Select all

timerfd_create()
timerfd_settime()
read()
epoll()
Pour comprendre les anciens programmes UNIX

Code: Select all

alarm()
setitimer()
33. Mini-projet : timer périodique moderne avec timerfd

Objectif : créer un programme qui affiche un message toutes les secondes pendant 10 secondes.

Code: Select all

#define _GNU_SOURCE

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <time.h>

int main(void)
{
    int fd;
    struct itimerspec its = {0};
    uint64_t expirations;
    int total = 0;

    fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
    if (fd == -1) {
        perror("timerfd_create");
        return EXIT_FAILURE;
    }

    its.it_value.tv_sec = 1;
    its.it_interval.tv_sec = 1;

    if (timerfd_settime(fd, 0, &its, NULL) == -1) {
        perror("timerfd_settime");
        close(fd);
        return EXIT_FAILURE;
    }

    while (total < 10) {
        if (read(fd, &expirations, sizeof(expirations)) != sizeof(expirations)) {
            perror("read");
            close(fd);
            return EXIT_FAILURE;
        }

        total += expirations;

        printf("tick : total = %d\n", total);
    }

    close(fd);
    return EXIT_SUCCESS;
}
Compilation

Code: Select all

gcc mini_timerfd.c -o mini_timerfd
34. Résumé final

Ce chapitre montre comment Linux permet de gérer le temps dans un programme.

Les points essentiels :
  • sleep() suspend simplement un programme.
  • nanosleep() permet un sommeil plus précis.
  • alarm() envoie SIGALRM après un délai.
  • setitimer() crée un timer UNIX classique, one-shot ou périodique.
  • clock_gettime() permet de lire une horloge.
  • CLOCK_MONOTONIC est le meilleur choix pour mesurer une durée.
  • timer_create() crée un timer POSIX moderne.
  • timer_settime() arme ou désarme un timer POSIX.
  • timer_getoverrun() indique les expirations ratées.
  • timerfd_create() crée un timer utilisable comme un fichier.
  • timerfd est très pratique avec poll(), select() et epoll().
À retenir absolument

Code: Select all

Pour attendre :
    sleep(), nanosleep()

Pour mesurer une duree :
    clock_gettime(CLOCK_MONOTONIC, ...)

Pour un timer simple historique :
    alarm(), setitimer()

Pour un timer POSIX moderne :
    timer_create(), timer_settime()

Pour un timer Linux propre dans une event loop :
    timerfd_create(), timerfd_settime(), read(), poll(), epoll()
35. Schéma mental final

Code: Select all

                 Gestion du temps sous Linux
                            |
        ------------------------------------------------
        |                 |              |             |
     Dormir            Mesurer        Timers        Event loop
        |                 |              |             |
 sleep()          clock_gettime()    alarm()       timerfd
 nanosleep()      CLOCK_MONOTONIC    setitimer()   poll/epoll
 clock_nanosleep()                 timer_create()
Conclusion

Pour un apprentissage système moderne, le plus important est de bien comprendre CLOCK_MONOTONIC, nanosleep(), les timers POSIX, et surtout timerfd si on veut coder des programmes réseau ou événementiels propres sous Linux.

Who is online

Users browsing this forum: No registered users and 1 guest