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().
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().
- 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.
- 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.
- 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.
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
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.
Timer one-shot
Un timer one-shot expire une seule fois.
Exemple :
Code: Select all
Dans 5 secondes, envoie SIGALRM.
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.
- it_value : délai avant la première expiration ;
- it_interval : délai entre les expirations suivantes.
3. alarm() — timer simple avec SIGALRM
Prototype
Code: Select all
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
Exemple :
Code: Select all
alarm(5);
Annuler une alarme
Code: Select all
alarm(0);
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);
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;
}
- On installe un handler pour SIGALRM.
- On appelle alarm(3).
- Après 3 secondes, le processus reçoit SIGALRM.
- Le handler est exécuté.
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);
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
};
- it_value : indique dans combien de temps le timer expire.
- it_interval : indique tous les combien il se répète.
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
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;
}
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);
Exemple :
Code: Select all
struct itimerval curr;
getitimer(ITIMER_REAL, &curr);
- curr.it_value contient le temps restant avant la prochaine expiration.
- curr.it_interval contient l’intervalle périodique.
Prototype
Code: Select all
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
Exemple :
Code: Select all
sleep(5);
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);
7. nanosleep() — sommeil plus précis
Prototype
Code: Select all
#include <time.h>
int nanosleep(const struct timespec *req,
struct timespec *rem);
Code: Select all
struct timespec {
time_t tv_sec; // secondes
long tv_nsec; // nanosecondes
};
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;
}
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;
}
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);
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);
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);
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
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;
}
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);
Exemple :
Code: Select all
struct timespec ts;
ts.tv_sec = 2;
ts.tv_nsec = 0;
clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
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);
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;
}
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.
Code: Select all
timer_create()
timer_settime()
timer_gettime()
timer_delete()
timer_getoverrun()
Prototype
Code: Select all
#include <signal.h>
#include <time.h>
int timer_create(clockid_t clockid,
struct sigevent *sevp,
timer_t *timerid);
- clockid : horloge utilisée par le timer.
- sevp : méthode de notification.
- timerid : identifiant du timer créé.
Code: Select all
timer_t tid;
timer_create(CLOCK_MONOTONIC, NULL, &tid);
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;
};
Code: Select all
SIGEV_NONE
SIGEV_SIGNAL
SIGEV_THREAD
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);
Code: Select all
struct itimerspec {
struct timespec it_interval; // repetition
struct timespec it_value; // premiere expiration
};
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);
- première expiration dans 5 secondes ;
- ensuite expiration toutes les 2 secondes.
Pour désarmer un timer, on met it_value à zéro.
Code: Select all
struct itimerspec its = {0};
timer_settime(tid, 0, &its, NULL);
Prototype
Code: Select all
#include <time.h>
int timer_gettime(timer_t timerid,
struct itimerspec *curr_value);
- le temps restant avant la prochaine expiration ;
- l’intervalle périodique du timer.
Code: Select all
struct itimerspec curr;
timer_gettime(tid, &curr);
Prototype
Code: Select all
#include <time.h>
int timer_delete(timer_t timerid);
Quand on a fini de l’utiliser, il faut le supprimer.
Code: Select all
timer_delete(tid);
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;
}
- 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.
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.
Code: Select all
#include <time.h>
int timer_getoverrun(timer_t timerid);
À 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;
}
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()
- des sockets réseau ;
- des fichiers ;
- des pipes ;
- des signaux via signalfd ;
- des timers via timerfd.
Prototype
Code: Select all
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
Code: Select all
int fd = timerfd_create(CLOCK_MONOTONIC, 0);
Code: Select all
TFD_CLOEXEC
TFD_NONBLOCK
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);
Code: Select all
struct itimerspec {
struct timespec it_interval;
struct timespec it_value;
};
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);
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));
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
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;
}
Code: Select all
gcc timerfd_demo.c -o timerfd_demo
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
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;
}
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.
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;
}
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);
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
Exemple mental
Code: Select all
sleep(10);
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()
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;
}
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);
Code: Select all
clock_gettime(CLOCK_MONOTONIC, &start);
Dans un handler :
Code: Select all
printf("signal\n"); // a eviter
Code: Select all
write(1, "signal\n", 7);
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);
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
Pour dormir simplement
Code: Select all
sleep()
Code: Select all
nanosleep()
Code: Select all
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)
Code: Select all
clock_gettime(CLOCK_MONOTONIC, ...)
Code: Select all
timer_create()
timer_settime()
Code: Select all
timerfd_create()
timerfd_settime()
read()
epoll()
Code: Select all
alarm()
setitimer()
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;
}
Code: Select all
gcc mini_timerfd.c -o mini_timerfd
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().
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()
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()
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.
