Approche basique avec setitimer
L'utilisation de la fonction setitimer combinée au signal SIGALRM constitue la méthode la plus directe pour implémenter un minuteur sous Linux. Cette approche est idéale pour les besoins simples ne nécessitant qu'un seul minuteur global au sein du processus.
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
void handle_alarm_signal(int signum) {
if (signum == SIGALRM) {
fputs("Le minuteur a expiré.\n", stdout);
}
}
void configure_one_shot_timer(unsigned int delay_sec) {
struct itimerval timer_config;
timer_config.it_value.tv_sec = delay_sec;
timer_config.it_value.tv_usec = 0;
timer_config.it_interval.tv_sec = 0;
timer_config.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer_config, NULL);
}
int main(void) {
signal(SIGALRM, handle_alarm_signal);
configure_one_shot_timer(2);
while (1) {
pause();
}
return 0;
}
API POSIX timer_create
Pour des architectures plus complexes, l'API POSIX fournit timer_create, qui permet la gestion de multiples minuteurs indépendatns avec des mécanismes de notification hautement configurables.
Notification par signal (SIGEV_SIGNAL)
Cette méthode délivre un signal asynchrone à l'expiration du minuteur. Elle est particulièrement adaptée aux applications nécessitant une interruption immédiate du flux d'exécution principal pour traiter l'événement.
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void custom_signal_handler(int sig, siginfo_t *si, void *unused) {
if (sig == SIGUSR1) {
printf("Expiration du minuteur, contexte: %s\n", (char *)si->si_value.sival_ptr);
}
}
int main(void) {
timer_t id_timer_a, id_timer_b;
struct sigevent evt_a, evt_b;
struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = custom_signal_handler;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR1, &act, NULL);
evt_a.sigev_notify = SIGEV_SIGNAL;
evt_a.sigev_signo = SIGUSR1;
evt_a.sigev_value.sival_ptr = "Contexte_A";
timer_create(CLOCK_MONOTONIC, &evt_a, &id_timer_a);
evt_b.sigev_notify = SIGEV_SIGNAL;
evt_b.sigev_signo = SIGUSR1;
evt_b.sigev_value.sival_ptr = "Contexte_B";
timer_create(CLOCK_MONOTONIC, &evt_b, &id_timer_b);
struct itimerspec spec_a = {{0, 0}, {3, 0}};
struct itimerspec spec_b = {{0, 0}, {7, 0}};
timer_settime(id_timer_a, 0, &spec_a, NULL);
timer_settime(id_timer_b, 0, &spec_b, NULL);
while (1) {
pause();
}
return 0;
}
Notification par thread (SIGEV_THREAD)
Plutôt que d'interrompre le flux principal via un signal, cette approche délègue le traitement à une fonction exécutée dans un nouveau thread. Cela permet d'effectuer des tâches complexes et bloquantes sans les restrictions imposées aux gestionnaires de signaux.
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void thread_callback(union sigval val) {
printf("Tâche du minuteur exécutée dans un thread, données: %s\n", (char *)val.sival_ptr);
sleep(1);
}
int main(void) {
timer_t t_id_1, t_id_2;
struct sigevent cfg_1, cfg_2;
cfg_1.sigev_notify = SIGEV_THREAD;
cfg_1.sigev_notify_function = thread_callback;
cfg_1.sigev_value.sival_ptr = "Données_1";
cfg_1.sigev_notify_attributes = NULL;
timer_create(CLOCK_REALTIME, &cfg_1, &t_id_1);
cfg_2.sigev_notify = SIGEV_THREAD;
cfg_2.sigev_notify_function = thread_callback;
cfg_2.sigev_value.sival_ptr = "Données_2";
cfg_2.sigev_notify_attributes = NULL;
timer_create(CLOCK_REALTIME, &cfg_2, &t_id_2);
struct itimerspec ts_1 = {{0, 0}, {4, 0}};
struct itimerspec ts_2 = {{0, 0}, {8, 0}};
timer_settime(t_id_1, 0, &ts_1, NULL);
timer_settime(t_id_2, 0, &ts_2, NULL);
while (1) {
pause();
}
return 0;
}
- Gestion des ressources : Un nouveau thread est instancié à chaque expiration. Pour les minuteurs à haute fréquence, il est crucial d'implémenter un pool de threads afin d'éviter l'épuisement des ressources système.
- Sécurité des threads : L'accès aux ressources partagées depuis la fonction de rappel doit être strictement protégé par des primitives de synchronisation telles que les mutex.
Paramètres de la structure sigevent
Le champ sigev_notify de la structure sigevent détermine le mécanisme de notification utilisé lors de l'expiration du minuteur :
- SIGEV_NONE : Aucune notification n'est générée. Utilisé lorsque le minuteur sert uniquement à suivre le temps écoulé sans action asnychrone.
- SIGEV_SIGNAL : Délivre un signal au processus. Requiert la configuration de
sigev_signoet permet le passage de données viasigev_value. - SIGEV_THREAD : Invoque une fonction spécifique dans un nouveau thread. Requiert la définition de
sigev_notify_functionet permet le passage d'arguments viasigev_value. - SIGEV_THREAD_ID : Spécifique à Linux (et POSIX.1-2008), permet d'envoyer un signal à un thread précis au sein du processus, offrant un contrôle granulaire dans les architectures multithreadées. Requiert
sigev_notify_thread_id.