Le *namespace* `time` {#time-ns} --------------------- ### Introduction L'espace de noms `time`, introduit dans Linux 5.6, permet de virtualiser les horloges `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME` pour des processus. Cela est particulièrement utile pour la migration de conteneurs ou pour tester des applications sensibles au temps. ::::: {.warning} Contrairement à ce que son nom pourrait suggérer, le *namespace* `time` ne permet **pas** de virtualiser l'heure système (l'horloge temps réel). Il ne virtualise que les compteurs monotones. ::::: ### Utilisation pratique Voyons comment utiliser le *namespace* `time` pour modifier le temps d'exécution perçu par un processus. #### Lire les horloges monotones\ Commençons par observer les valeurs actuelles de nos horloges :
```bash 42sh$ cat /proc/uptime 123456.78 987654.32 ```
Le premier nombre représente le temps depuis le démarrage du système (équivalent à `CLOCK_BOOTTIME`), et le second le temps CPU cumulé en mode idle. On peut également utiliser un petit programme C pour afficher les horloges :
```c #include #include int main(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); printf("CLOCK_MONOTONIC: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); clock_gettime(CLOCK_BOOTTIME, &ts); printf("CLOCK_BOOTTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); return 0; } ```
::::: {.code} Pour compiler ce programme :
```bash 42sh$ gcc -o show_clocks show_clocks.c ```
::::: #### Créer un *namespace* `time` avec décalage\ Pour créer un nouveau *namespace* `time`, il faut d'abord le dissocier puis configurer les décalages (offsets) avant de lancer un processus enfant. Le fichier `/proc//timens_offsets` permet de configurer les décalages pour les horloges. Le format est le suivant :
``` ```
Où `` peut être : - `monotonic` pour `CLOCK_MONOTONIC` - `boottime` pour `CLOCK_BOOTTIME` Voici un script shell pour créer un conteneur avec un temps décalé de 10 jours dans le futur :
```bash 42sh# unshare --time /bin/bash intimens# echo "monotonic 864000 0" > /proc/self/timens_offsets intimens# echo "boottime 864000 0" > /proc/self/timens_offsets intimens# exec /bin/bash intimens-child# ./show_clocks CLOCK_MONOTONIC: 987654.123456789 CLOCK_BOOTTIME: 987654.123456789 ```
::::: {.warning} **Important :** Les décalages doivent être configurés **avant** de créer le premier processus enfant. Une fois qu'un processus enfant existe dans le *namespace*, les offsets ne peuvent plus être modifiés. C'est pourquoi nous utilisons `exec` pour remplacer le shell actuel. ::::: #### Exemple en C\ Voici un exemple complet en C pour créer un *namespace* `time` avec un décalage :
```c #define _GNU_SOURCE #include #include #include #include #include #include #include #include static int child_func(void *arg) { struct timespec ts; // Attendre que le parent configure les offsets sleep(1); clock_gettime(CLOCK_MONOTONIC, &ts); printf("Child - CLOCK_MONOTONIC: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); clock_gettime(CLOCK_BOOTTIME, &ts); printf("Child - CLOCK_BOOTTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); return 0; } #define STACK_SIZE (1024 * 1024) static char child_stack[STACK_SIZE]; int main(void) { pid_t pid; int fd; char path[256]; struct timespec ts; // Afficher les horloges du parent clock_gettime(CLOCK_MONOTONIC, &ts); printf("Parent - CLOCK_MONOTONIC: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); // Se dissocier du namespace time if (unshare(CLONE_NEWTIME) == -1) { perror("unshare"); exit(EXIT_FAILURE); } // Configurer les offsets (décalage de 1000 secondes) fd = open("/proc/self/timens_offsets", O_WRONLY); if (fd == -1) { perror("open timens_offsets"); exit(EXIT_FAILURE); } if (write(fd, "monotonic 1000 0\n", 17) != 17) { perror("write monotonic offset"); exit(EXIT_FAILURE); } if (write(fd, "boottime 1000 0\n", 16) != 16) { perror("write boottime offset"); exit(EXIT_FAILURE); } close(fd); // Créer un processus enfant qui verra les nouvelles horloges pid = clone(child_func, child_stack + STACK_SIZE, SIGCHLD, NULL); if (pid == -1) { perror("clone"); exit(EXIT_FAILURE); } waitpid(pid, NULL, 0); return 0; } ```
::::: {.code} Pour compiler :
```bash 42sh$ gcc -o time_ns_demo time_ns_demo.c 42sh$ ./time_ns_demo Parent - CLOCK_MONOTONIC: 123456.789012345 Child - CLOCK_MONOTONIC: 124456.789012345 Child - CLOCK_BOOTTIME: 124456.789012345 ```
On voit bien le décalage de 1000 secondes entre le parent et l'enfant. ::::: ### Cas d'usage pratiques #### Migration de conteneurs\ Lors de la migration d'un conteneur d'une machine à une autre, les processus peuvent avoir des timers ou des alarmes basées sur `CLOCK_MONOTONIC`. Si on ne virtualise pas ces horloges, tous ces timers seraient incorrects après la migration. Le *namespace* `time` permet de restaurer l'état des horloges monotones lors de la restauration d'un conteneur. #### Tests et simulation\ Pour tester des applications qui utilisent des timeouts ou des mécanismes de retry avec backoff exponentiel, il peut être utile de virtualiser le temps pour accélérer les tests sans modifier le code de l'application. ### Limitations - Seules les horloges `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME` peuvent être virtualisées - L'heure système (`CLOCK_REALTIME`) reste identique pour tous les processus - Les offsets ne peuvent être configurés qu'une seule fois, avant la création du premier processus enfant - Les décalages ne peuvent pas être négatifs (on ne peut pas remonter dans le temps) ### Aller plus loin {-} Pour plus d'informations sur le *namespace* `time`, consultez : - `time_namespaces(7)` - page de manuel - [Time Namespace support](https://lwn.net/Articles/766089/) : - [Checkpoint/Restore and Time Namespace](https://lwn.net/Articles/801652/) :