263 lines
6.4 KiB
Markdown
263 lines
6.4 KiB
Markdown
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 :
|
|
|
|
<div lang="en-US">
|
|
```bash
|
|
42sh$ cat /proc/uptime
|
|
123456.78 987654.32
|
|
```
|
|
</div>
|
|
|
|
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 :
|
|
|
|
<div lang="en-US">
|
|
```c
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
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;
|
|
}
|
|
```
|
|
</div>
|
|
|
|
::::: {.code}
|
|
|
|
Pour compiler ce programme :
|
|
|
|
<div lang="en-US">
|
|
```bash
|
|
42sh$ gcc -o show_clocks show_clocks.c
|
|
```
|
|
</div>
|
|
|
|
:::::
|
|
|
|
|
|
#### 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/<PID>/timens_offsets` permet de configurer les décalages pour
|
|
les horloges. Le format est le suivant :
|
|
|
|
<div lang="en-US">
|
|
```
|
|
<clock-id> <offset-secs> <offset-nanosecs>
|
|
```
|
|
</div>
|
|
|
|
Où `<clock-id>` 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 :
|
|
|
|
<div lang="en-US">
|
|
```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
|
|
```
|
|
</div>
|
|
|
|
::::: {.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 :
|
|
|
|
<div lang="en-US">
|
|
```c
|
|
#define _GNU_SOURCE
|
|
#include <sched.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
|
|
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;
|
|
}
|
|
```
|
|
</div>
|
|
|
|
::::: {.code}
|
|
|
|
Pour compiler :
|
|
|
|
<div lang="en-US">
|
|
```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
|
|
```
|
|
</div>
|
|
|
|
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/) : <https://lwn.net/Articles/766089/>
|
|
- [Checkpoint/Restore and Time Namespace](https://lwn.net/Articles/801652/) : <https://lwn.net/Articles/801652/>
|