Fonctionnement des minuteurs d'intervalle sous Linux
Le noyau Linux expose une interface précise pour le suivi du temps d'exécution via les appels système setitimer() et getitimer() (déclarés dans <sys/time.h>). Ces mécanismes reposent sur la structure struct itimerval, qui définit la valeur initiale (it_value) et la période de rechargement (it_interval) exprimées en secondes et microsecondes.
Trois classes de minuteurs sont disponibles :
ITIMER_REAL: Décompte le temps réel (horloge système). Il décrémente de manière continue, indépendamment de l'état du processeur, et déclenche le signalSIGALRM.ITIMER_VIRTUAL: Mesure uniquement le temps passé par le processus en espace utilisateur. Il est suspendu pendant les appels système et l'ordonnancement, et émetSIGVTALRM.ITIMER_PROF: Comptabilise le temps CPU total (utilisateur + noyau). Il inclut l'exécution du code applicatif ainsi que le temps passé dans le noyau pour les services demandés par le processus. Il génèreSIGPROF.
La différence entre ITIMER_PROF et ITIMER_VIRTUAL correspond précisément à la durée d'exécution en mode noyau pour un processus donné.
Implémetnation d'une expiration cyclique
L'exemple ci-dessous confgiure un mintueur réel qui expire chaque seconde. Le programme interroge régulièrement l'état du compteur et notifie une fonction de rappel lorsque la seconde est franchie.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
static volatile unsigned long seconds_elapsed = 0;
static struct itimerval timer_cfg;
static void notify_progress(int signum) {
if (signum == SIGUSR1) {
printf("Top : 1 seconde supplémentaire validée.\n");
} else if (signum == SIGALRM) {
printf("Cycle finalisé. Durée totale comptabilisée : %lu s\n", seconds_elapsed);
seconds_elapsed = 0;
}
}
int main(void) {
struct sigaction sa;
sa.sa_handler = notify_progress;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGALRM, &sa, NULL);
timer_cfg.it_value.tv_sec = 1;
timer_cfg.it_value.tv_usec = 0;
timer_cfg.it_interval.tv_sec = 1;
timer_cfg.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL, &timer_cfg, NULL) == -1) {
perror("Échec configuration minuteur");
return EXIT_FAILURE;
}
struct itimerval snapshot;
long prev_remainder = timer_cfg.it_value.tv_sec;
for (int cycle = 0; cycle < 5; cycle++) {
getitimer(ITIMER_REAL, &snapshot);
long current_remainder = snapshot.it_value.tv_sec;
if (current_remainder < prev_remainder) {
raise(SIGUSR1);
seconds_elapsed++;
prev_remainder = current_remainder;
}
usleep(50000);
}
return EXIT_SUCCESS;
}
Dissociation du temps utilisateur et du temps système
Pour isoler la charge imposée au noyau par un processus, il est nécessaire d'activer simultanément les trois compteurs et de comparer leurs valeurs résiduelles. La routine suivante récupère l'état des trois minuteurs et calcule la durée d'exécution en espace noyau par soustraction.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
static long acc_real = 0, acc_cpu = 0, acc_user = 0;
static struct itimerval base_cfg;
static void display_timers(int signum) {
struct itimerval r, c, u;
getitimer(ITIMER_REAL, &r);
getitimer(ITIMER_PROF, &c);
getitimer(ITIMER_VIRTUAL, &u);
long elapsed_real = 10 - r.it_value.tv_sec;
long elapsed_cpu = 10 - c.it_value.tv_sec;
long elapsed_user = 10 - u.it_value.tv_sec;
printf("Mesure | Réel : %ld s | CPU : %ld s | Utilisateur : %ld s\n",
elapsed_real, elapsed_cpu, elapsed_user);
if (elapsed_cpu > elapsed_user) {
printf(" -> Délai Noyau déduit : %ld s\n", elapsed_cpu - elapsed_user);
}
switch (signum) {
case SIGALRM: acc_real = elapsed_real; break;
case SIGPROF: acc_cpu = elapsed_cpu; break;
case SIGVTALRM: acc_user = elapsed_user; break;
}
}
int main(void) {
signal(SIGALRM, display_timers);
signal(SIGPROF, display_timers);
signal(SIGVTALRM, display_timers);
base_cfg.it_value.tv_sec = 10;
base_cfg.it_value.tv_usec = 0;
base_cfg.it_interval.tv_sec = 10;
base_cfg.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &base_cfg, NULL);
setitimer(ITIMER_PROF, &base_cfg, NULL);
setitimer(ITIMER_VIRTUAL, &base_cfg, NULL);
for (int i = 0; i < 3; i++) {
sleep(3);
raise(SIGUSR1);
}
return 0;
}
Supervision temporelle dans un environnement multi-processus
Dans un contexte de production, les processus alternent entre calculs intensifs et appels système. Le programme suivant instancie un processus parent et deux processus fils, chacun exécutant une fonction récursive consommatrice de cycles. Chaque entité maintient ses propres compteurs et restitue une analyse complète de son temps d'exécution à la fin du traitement.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#define WORKER_COUNT 3
#define TIMER_CYCLES 10
static long worker_real[WORKER_COUNT] = {0};
static long worker_prof[WORKER_COUNT] = {0};
static long worker_virt[WORKER_COUNT] = {0};
static int active_worker_idx = 0;
static void update_counters(int sig) {
switch (sig) {
case SIGALRM: worker_real[active_worker_idx] += TIMER_CYCLES; break;
case SIGPROF: worker_prof[active_worker_idx] += TIMER_CYCLES; break;
case SIGVTALRM: worker_virt[active_worker_idx] += TIMER_CYCLES; break;
}
}
unsigned long compute_fibonacci(unsigned int depth) {
return (depth <= 1) ? depth : compute_fibonacci(depth - 1) + compute_fibonacci(depth - 2);
}
void execute_task(int id, unsigned int target) {
active_worker_idx = id;
struct itimerval cfg = {0};
cfg.it_value.tv_sec = TIMER_CYCLES;
cfg.it_interval.tv_sec = TIMER_CYCLES;
signal(SIGALRM, update_counters);
signal(SIGPROF, update_counters);
signal(SIGVTALRM, update_counters);
setitimer(ITIMER_REAL, &cfg, NULL);
setitimer(ITIMER_PROF, &cfg, NULL);
setitimer(ITIMER_VIRTUAL, &cfg, NULL);
unsigned long result = compute_fibonacci(target);
struct itimerval rem_r, rem_p, rem_u;
getitimer(ITIMER_REAL, &rem_r);
getitimer(ITIMER_PROF, &rem_p);
getitimer(ITIMER_VIRTUAL, &rem_u);
long part_r = TIMER_CYCLES - rem_r.it_value.tv_sec;
long part_p = TIMER_CYCLES - rem_p.it_value.tv_sec;
long part_u = TIMER_CYCLES - rem_u.it_value.tv_sec;
long total_r = worker_real[id] + part_r;
long total_p = worker_prof[id] + part_p;
long total_u = worker_virt[id] + part_u;
long total_k = total_p - total_u;
printf("Tâche %d | Fib(%u)=%lu\n", id, target, result);
printf(" -> Réel: %lds | CPU: %lds | Util: %lds | Noyau: %lds\n\n",
total_r, total_p, total_u, total_k);
exit(EXIT_SUCCESS);
}
int main(void) {
const unsigned int target_depth = 39;
int exit_status;
for (int i = 0; i < WORKER_COUNT; i++) {
pid_t pid = fork();
if (pid == 0) {
execute_task(i, target_depth);
}
}
for (int i = 0; i < WORKER_COUNT; i++) {
wait(&exit_status);
}
return EXIT_SUCCESS;
}