Mise à jour, 11 octobre 2026, 16 h 30 : j’ai depuis flashé le BIOS 1.30. Il ne corrige pas le décalage. tscsync aligne toujours les cœurs, mais sur deux démarrages en mode sync le noyau a quand même lâché le TSC, par son watchdog des clocksources et non par le contrôle de warp : BIOS 1.30 : la synchro tient, le TSC tombe quand même. Avec
tsc=nowatchdogen plus, le TSC a tenu, et cinq minutes de mesure contre l’horloge temps réel n’ont trouvé ni warp ni dérive : tsc=nowatchdog : cinq minutes, aucun warp. Un démarrage n’est pas une série de démarrages, donc je dis que ça tient, pas que c’est corrigé.
À chaque démarrage, le même avertissement
Ceci traînait dans le journal du noyau de mon ThinkPad E595 à chaque démarrage, sans exception :
TSC synchronization [CPU#0 -> CPU#2]:
Measured 6601281288 cycles TSC warp between CPUs, turning off TSC clock.
tsc: Marking TSC unstable due to check_tsc_sync_source failed
clocksource: Switched to clocksource hpet
Rien n’a planté. Rien n’est apparu sur le bureau. La machine a tranquillement fait tourner toute son horloge sur une puce de timer plus lente dès le démarrage, et elle aurait continué indéfiniment si je n’avais pas lu le journal pour tout autre chose.
Le Time Stamp Counter (TSC) est un compteur dans chaque cœur de CPU qui avance à cadence fixe. Le lire tient en une seule instruction. Linux s’en sert comme horloge principale, mais seulement si tous les cœurs sont d’accord sur le compte1. Si un cœur est en retard, le noyau ne peut pas s’y fier et se rabat sur le High Precision Event Timer (HPET), un équipement de timer séparé. L’auteur de tscsync a mesuré une lecture d’horloge à environ 1,2 µs sur le HPET contre quelques dizaines de nanosecondes sur le TSC, et le noyau et le bureau lisent l’horloge des dizaines de milliers de fois par seconde1. C’est de la latence et de la batterie, payées à chaque démarrage, pour rien.
Alors, de combien le cœur était-il en retard ? Plus de trois secondes. Sur un portable à 2,1 GHz, ça fait 6,6 milliards de cycles.
Ce que le firmware transmet
Le message nomme CPU 0 et CPU 2, mais le fautif n’est pas CPU 2. Mesuré par tscsync depuis le chargeur d’amorçage, puis par le noyau une fois lancé, le tableau est le même à chaque fois :
| Cœur | Décalage par rapport à CPU 0 | Ce que ça veut dire |
|---|---|---|
| CPU 0, le processeur d’amorçage (BSP) | 0 | la référence, et l’intrus |
| CPU 1 à 7, les autres cœurs (AP) | environ +6,2 à +6,8 milliards de cycles | tous d’accord entre eux à 32 cycles près |
| Écart à 2 096 MHz | 2,9 à 3,3 secondes | le processeur d’amorçage est en retard, à chaque démarrage |
Sept cœurs sont d’accord. Un a trois secondes de retard. Le firmware démarre le processeur d’amorçage, fait son travail dessus, et passe la main au chargeur d’amorçage sans jamais remettre ce compteur-là en ligne avec les autres.
Pourquoi Linux ne peut-il pas simplement corriger ça ? Sur les CPU Intel, il existe un registre, IA32_TSC_ADJUST, qui permet au noyau d’ajuster le compteur d’un cœur, et le code de synchronisation du noyau s’en sert quand il est là2. Les CPU AMD Zen ne l’ont pas. Sans lui, rien dans Linux ne peut réparer le décalage, et la tentative d’écrire les compteurs directement depuis le noyau (tsc=directsync) a été rejetée en amont comme peu fiable3.
Le noyau fait donc la seule chose honnête qui reste. Il mesure, il constate que le temps recule entre deux cœurs, il affiche l’avertissement et il arrête d’utiliser le TSC2.
Quarante démarrages, quarante warps
Un démarrage, c’est une anecdote. Le journal de cette machine remonte au 30 mai 2026, alors je lui ai posé la question.
| Démarrages | Warp, cycles | Warp à 2 096 MHz | |
|---|---|---|---|
| Du 30 mai au 11 octobre 2026 | 40 sur 40 | 6,16 à 9,88 milliards | 2,94 à 4,72 s |
| Médiane | 6,57 milliards | 3,14 s | |
| Après une coupure complète d’environ 27 s | 1 | 6,60 milliards | 3,15 s |
| Avec tscsync en mode sync, BIOS 1.28 | 1 | aucun consigné | TSC conservé |
| BIOS 1.30, tscsync désactivé | 1 | 6,13 milliards | 2,92 s |
| BIOS 1.30, tscsync en mode sync | 2 | aucun consigné | TSC lâché par le watchdog, voir plus bas |
BIOS 1.30, mode sync et tsc=nowatchdog | 1 | aucun consigné | TSC conservé |
À chaque démarrage. Démarrage à froid, redémarrage, après une coupure assez longue pour qu’on s’attende à ce que tout résidu ait disparu. La valeur bouge, ce qui colle avec ce qu’a trouvé l’auteur de tscsync sur un Lenovo Legion : là-bas, le warp suit presque parfaitement le temps que la machine passe dans le firmware avant que le chargeur d’amorçage démarre3. Un démarrage qui reste plus longtemps sur le logo en sort plus en retard.
La correction tentante, c’est tsc=reliable sur la ligne de commande du noyau, qui dit à Linux d’arrêter de vérifier. Ne le faites pas. Les cœurs ont vraiment trois secondes d’écart, et couper le contrôle n’y change rien ; ça laisse juste le noyau utiliser une horloge qui saute en arrière quand une tâche passe d’un cœur à l’autre. Le README de tscsync dit la même chose, et nomme les saccades audio et vidéo qui s’ensuivent1.
Puis-je demander pourquoi ça sort encore ?
Je ne demande pas qui. Je demande ce qui, dans le processus de Lenovo, a laissé sortir un BIOS qui laisse un cœur trois secondes en retard à chaque démarrage.
La réponse est dans les notes de conception de tscsync, et elle n’est pas flatteuse. Fisher-Price OS (Windows) écrit lui-même les TSC au démarrage3. Un portable validé uniquement là-dessus passe, parce que le système d’exploitation range discrètement derrière le firmware. Linux garde son contrôle de sécurité actif et rapporte ce qu’on lui a donné, et c’est exactement pour ça que le défaut apparaît là et nulle part ailleurs.
Le plus gênant, c’est que ça aurait déjà été corrigé. Des utilisateurs rapportent que Lenovo a corrigé la même classe de bug par mise à jour du BIOS sur les ThinkPad A485, T14s Gen 1 AMD et E4954. Le E495 est le petit frère 14 pouces de cette machine, et ils partagent un seul BIOS : le readme de Lenovo pour le paquet liste les deux5.
Mon E595 était en 1.28 et faisait un warp à chaque démarrage. Lenovo a publié depuis les versions 1.29 et 1.30, et aucune des deux listes de changements ne dit un mot du TSC ; la 1.30 corrige « system randomly freeze since BIOS V1.27 » et quelques problèmes de sécurité5. J’ai donc installé la 1.30 et mesuré à nouveau. Le premier démarrage dessus, tscsync désactivé, a consigné un warp de 6 126 446 316 cycles : 2,92 secondes, CPU#0 -> CPU#2, comme toujours. La 1.30 ne le corrige pas. Les deux modèles partagent un seul BIOS, donc si le rapport sur le E495 était juste, cette machine serait corrigée aussi. Le rapport ne cite aucune version de BIOS et ne renvoie à aucun journal4.
Lenovo va-t-il revenir sur un portable de 2019 pour un défaut que lui cache le système d’exploitation sur lequel il teste ? Non.
tscsync, mesuré d’abord
tscsync est un petit programme UEFI qui se lance depuis GRUB ou systemd-boot juste avant Linux. Il demande à chaque cœur son TSC via les propres services multiprocesseurs du firmware, calcule le décalage de chaque cœur par rapport au processeur d’amorçage, avance les compteurs en retard jusqu’à ce qu’ils soient d’accord, puis vérifie le résultat avec un test calqué sur le contrôle de warp du noyau1. Il n’écrit jamais dans la flash, la NVRAM ou les réglages du firmware ; les compteurs qu’il modifie se remettent à zéro à l’extinction1.
Ça fait beaucoup à confier à un chargeur d’amorçage, donc j’ai d’abord lu le code, au commit 230eaa9, avant de le compiler. L’installation s’est ensuite faite comme le projet le demande :
| Étape | Commande | Ce qui est revenu |
|---|---|---|
| Compilation | make | binaires GRUB et systemd-boot |
| Installation, lecture seule | sudo scripts/install.sh | mode mesure, rien d’écrit dans les compteurs |
| Extinction, démarrage, coup d’œil | tscsync-status | CPU 0 2 964 ms derrière les 7 AP, les AP d’accord à quelques microsecondes près |
| Activation | sudo scripts/set-mode.sh sync | prend effet au prochain démarrage |
Le mode mesure, c’est la partie à copier dans tout outil de ce genre. Il vous montre le défaut sur votre propre matériel avant d’avoir le droit de changer quoi que ce soit, et une protection contre les blocages le désactive si un démarrage n’atteint jamais Linux1.
Le premier démarrage en mode sync, tout droit sorti de tscsync-status :
survey (read-only):
cpu 1 apic 1: offset 6815086011 rtt 126 BAD
...
cpu 7 apic 7: offset 6815086010 rtt 378 BAD
0 ok, 7 not ok
moving BSP forward by ~6815086010 cycles to match cpu 7:
bsp vs cpu 7 apic 0: before 6815086001 after 3 rtt 357 iter 3 warps bsp/ap 0/0 OK
AP pass (corrects any AP still off):
7 ok, 0 not ok
total 121 ms
Et le noyau, une seconde plus tard :
clocksource: Switched to clocksource tsc-early
tsc: Refined TSC clocksource calibration: 2096.066 MHz
clocksource: Switched to clocksource tsc
Pas de ligne de warp. Pas de « unstable ». /sys/devices/system/clocksource/clocksource0/current_clocksource indique tsc. 121 ms de temps de démarrage pour obtenir l’horloge que le matériel était censé avoir depuis le début. C’était sous BIOS 1.28. La 1.30 n’a pas été aussi propre.
Les 21 nanosecondes que Linux a laissé passer
Il y a une ligne dans ce rapport qui a l’air pire qu’elle ne l’est, et elle mérite d’être expliquée plutôt que cachée. La passe de vérification finale de tscsync a signalé un cœur :
cpu 7 apic 7: offset 45 rtt 357 warps bsp/ap 0/0 BAD
6 ok, 1 not ok
RESULT: some APs out of sync (Linux will fall back to HPET)
45 cycles, c’est environ 21 nanosecondes. tscsync vise un accord à une dizaine de cycles près1, donc selon sa propre cible CPU 7 a raté, et il l’a dit. Puis Linux a démarré et n’est pas du tout retombé sur le HPET.
Les deux contrôles posent des questions différentes :
| Verdict de décalage de tscsync | Test de warp du noyau | |
|---|---|---|
| Question | à quelle distance sont les compteurs ? | le temps recule-t-il parfois entre les cœurs ? |
| Méthode | estimation à partir d’un aller-retour chronométré | les cœurs lisent à tour de rôle et comparent avec la dernière lecture2 |
| Seuil | environ 10 cycles | aucune lecture inférieure à la précédente |
| CPU 7 sur ce démarrage | 45 cycles, BAD | aucun warp, TSC conservé |
Le warps bsp/ap 0/0 sur cette même ligne, c’est tscsync qui exécute le test façon noyau. Il n’en a vu aucun. Le noyau non plus.
Le projet sait déjà que son verdict peut être plus strict que celui du noyau. Un IdeaPad de sa liste de matériel avait quatre cœurs signalés à 28 à 42 cycles que le noyau a acceptés, et depuis la 2.1 un aller-retour lent est rapporté comme UNSURE plutôt que BAD4. La règle du README pour ce cas est la bonne : c’est le contrôle du noyau qui tranche, et si la clocksource est tsc, il est passé1. Un aller-retour de 357 cycles n’est pas lent sur cette machine, donc c’est sorti en BAD. Un outil qui montre son raisonnement et laisse ensuite le dernier mot au noyau, c’est le bon ordre des choses.
BIOS 1.30 : la synchro tient, le TSC tombe quand même
Sous la 1.30, j’ai remis tscsync en mode sync et redémarré deux fois. Les deux fois, il a fait son travail. Il a avancé le processeur d’amorçage d’environ 6,17 milliards de cycles, tous les cœurs sont ressortis à quelques dizaines de cycles près, et le noyau n’a consigné aucun warp. Les deux fois, Linux a quand même fini sur le HPET quelques secondes plus tard :
tsc: Detected 2095.890 MHz processor
tsc: Refined TSC clocksource calibration: 2096.053 MHz
clocksource: Watchdog remote CPU 5 read timed out
clocksource: Marking clocksource tsc unstable due to frequency skew
clocksource: Watchdog hpet interval: 495136113ns
clocksource: Clocksource tsc interval: 496026671ns
tsc: Marking TSC unstable due to clocksource watchdog
C’est un autre contrôle. Le test de warp s’exécute à mesure que chaque cœur démarre et demande si le temps recule parfois entre les cœurs. Le watchdog des clocksources, lui, continue de tourner après le démarrage. Environ toutes les demi-secondes, il compare de combien le TSC a avancé et de combien le HPET a avancé, et s’ils divergent de plus d’une petite marge, il marque le TSC instable6. Une fois marqué, il le reste jusqu’au démarrage suivant.
tsc=nowatchdog ne saute que le second, alors que tsc=reliable saute les deux et masquerait un vrai warp.| Démarrage sous 1.30 | Contrôle de warp | Intervalle HPET | Intervalle TSC | TSC par rapport au HPET |
|---|---|---|---|---|
| Premier démarrage sync | passé | 495 426 793 ns | 495 006 210 ns | environ 850 ppm trop lent |
| Second démarrage sync | passé | 495 136 113 ns | 496 026 671 ns | environ 1 800 ppm trop rapide |
Alors, le TSC tourne-t-il à la mauvaise cadence ? Non. Machine démarrée, j’ai chronométré le TSC contre l’horloge brute du noyau basée sur le HPET pendant 30 secondes, par fenêtres de 5 secondes. Chaque fenêtre est sortie à 2 096,062 MHz, à environ 4 parties par million de la propre calibration du noyau. Un vrai défaut de cadence ne passe pas de lent à rapide entre deux démarrages pour ensuite disparaître. Quelque chose perturbe une fenêtre d’une demi-seconde tôt au démarrage, et la ligne remote CPU 5 read timed out juste avant désigne un cœur qui, brièvement, ne répond pas. Un firmware qui vole du temps aux CPU collerait. Je ne l’ai pas prouvé.
Deux démarrages, c’est toute la preuve disponible. Je ne peux pas encore dire si c’est la 1.30, le hasard, ou quelque chose que déclenche l’écriture de tscsync. Le seul démarrage sync sous 1.28 a gardé le TSC et les deux sous 1.30 l’ont perdu, et ça ne suffit pas pour accuser le BIOS.
Il existe une option du noyau plus ciblée que tsc=reliable pour ce cas. tsc=nowatchdog désactive le watchdog des clocksources pour le TSC et laisse tranquille le contrôle de warp au démarrage7. Si tscsync échouait un jour à synchroniser, le noyau attraperait quand même le warp et retomberait sur le HPET. C’était le test suivant. Les chiffres, et quelques suggestions de changements dans les rapports de tscsync lui-même, sont sur le gestionnaire de tickets du projet8.
tsc=nowatchdog : cinq minutes, aucun warp
J’ai ajouté tsc=nowatchdog et redémarré. tscsync a avancé le processeur d’amorçage de 6 207 104 421 cycles, le noyau n’a consigné ni warp ni écart de fréquence, et current_clocksource indiquait tsc. Premier démarrage sous 1.30 à le garder.
Ça prouve seulement que c’était le watchdog qui lâchait le TSC. Ça ne prouve pas qu’on peut tourner sur le TSC en toute sécurité, et avec le watchdog coupé, plus rien dans le noyau ne surveille. Alors je l’ai surveillé moi-même.
Ma vérification de cadence précédente ne servait à rien ici. Elle chronométrait le TSC contre CLOCK_MONOTONIC_RAW, ce qui allait tant que le noyau tournait sur le HPET. Sur le TSC, cette horloge est construite à partir du TSC, donc le test comparerait le compteur avec lui-même. Le HPET ne peut pas non plus être lu depuis l’espace utilisateur, parce que le noyau de Fedora est compilé sans CONFIG_HPET_MMAP. L’horloge temps réel (RTC), si. Elle tourne sur son propre quartz, et le noyau réveille un programme à chaque tick de son compteur de secondes9.
J’ai donc écrit une petite vérification, tsccheck. Elle s’épingle sur CPU 0 et lance quatre tests toutes les 15 secondes :
| Vérification | Comment | À quoi ressemble un défaut |
|---|---|---|
| Cadence | cycles TSC comptés entre les ticks de seconde de la RTC | des MHz qui dérivent dans un sens |
| Décalage | 3 000 allers-retours de CPU 0 vers chaque cœur, décalage pris sur le plus rapide | un décalage qui grandit |
| Warps | à chaque aller-retour, un cœur a-t-il lu moins qu’une valeur déjà vue | tout ce qui dépasse zéro |
| Clocksource | lecture de current_clocksource | autre chose que tsc |
L’aller-retour est le cœur de l’affaire. CPU 0 lit son compteur et le transmet, l’autre cœur lit le sien et le renvoie, et CPU 0 relit. Si les cœurs sont d’accord, la lecture du milieu doit tomber entre les deux autres :
La boucle qui fait ça :
for (int i = 0; i < ITERS; i++) {
uint64_t t0 = rdtsc_o(); /* CPU 0 */
atomic_store(&pp.t0, t0);
atomic_store(&pp.seq, 2 * i + 1); /* the other core reads t1 now */
while (atomic_load(&pp.seq) != 2 * i + 2)
;
uint64_t t2 = rdtsc_o(); /* CPU 0 again */
uint64_t t1 = atomic_load(&pp.t1);
if (t1 < t0 || t2 < t1)
r.warps++; /* time went backwards */
if (i > 100 && t2 - t0 < r.rtt) {
r.rtt = t2 - t0;
r.offset = (int64_t)(t1 - (t0 + (t2 - t0) / 2));
}
}
Le programme entier est du C simple pour Linux x86-64, sans rien de lié à tscsync ou à ce portable. Il a besoin de root pour /dev/rtc0, et le troisième argument est le chiffre de votre propre noyau, tiré de journalctl -k -b | grep 'Refined TSC' :
gcc -O2 -Wall -pthread -o tsccheck tsccheck.c
sudo ./tsccheck 15 20 2096.073
SHA-256 02eba1966a0097196acd5dfdcad9c4b315cafc1854c60f34c5b56080e6c38408.
Vingt échantillons sur cinq minutes, juste après le démarrage :
| Mesure | Résultat |
|---|---|
| Clocksource | tsc dans les 20 échantillons, aucun message du noyau |
| Warps | 0 sur environ 420 000 allers-retours |
| Pire décalage par rapport à CPU 0 | 85 cycles, environ 41 ns, aucune tendance |
| Cadence sur toute la durée | 2 096,027 MHz, entre 2 096,022 et 2 096,031 dès la première minute |
| Cadence par fenêtre de 15 secondes | 2 095,943 à 2 096,104 MHz |
Les fenêtres ont l’air pires qu’elles ne le sont. Un tick RTC en retard rend une fenêtre longue et la suivante courte, donc les erreurs viennent par paires qui s’annulent : 61 parties par million (ppm) trop lent à 30 secondes, 15 ppm trop rapide à 60. Une dérive ne s’annule pas d’elle-même. Le chiffre sur toute la durée a à peine bougé.
Le chiffre sur toute la durée reste 22 ppm sous les 2 096,073 MHz du noyau pendant les cinq minutes. C’est l’écart entre deux quartz, et il n’a pas bougé. chrony, qui pilote l’horloge contre des serveurs de temps sur Internet, l’avait à 16 à 19 ppm trop lent10. Même sens, même ordre de grandeur.
Les décalages sautent entre les mêmes quelques valeurs, 10, 21 et 33 cycles de part et d’autre, sans qu’aucun cœur ne s’éloigne. C’est le bruit d’un aller-retour d’environ 550 cycles. La vérification de tscsync en qualifierait encore certains de BAD. Le noyau, non.
Le TSC a-t-il tenu ? Oui, sur toutes les mesures que j’avais. Cinq minutes après un seul démarrage, ce n’est toujours pas une série de démarrages. La suite : la même vérification après une longue durée de fonctionnement, puis de nouveau sur quelques redémarrages de plus.
La correction a sa place dans le firmware
tscsync ne devrait pas avoir besoin d’exister. Son propre README le dit : les fabricants de firmware sont les bienvenus pour reprendre l’approche et le code, « which is where this should be fixed »1. Lenovo a le code source. L’auteur de tscsync n’avait que ce que son portable voulait bien lui dire, et 80 démarrages de journaux1.
C’est ça qui reste en travers. La correction a demandé à un inconnu extérieur à l’entreprise de mesurer, diagnostiquer et contourner, pendant que la société qui a vendu le matériel l’a validé sur le seul système d’exploitation qui cache le défaut. Une machine que vous ne pouvez pas réparer parce que son firmware est fermé n’est pas vraiment à vous, elle vous est prêtée sous conditions. Un firmware ouvert avec des mises à jour signées aurait permis de corriger ça une fois, proprement, par n’importe qui d’assez motivé pour regarder.
En attendant, beau travail de l’auteur de tscsync. Le mode mesure d’abord, une protection contre les blocages, les chiffres au grand jour et un verdict honnête même quand il contredit le noyau. C’est comme ça qu’on devrait construire les outils bas niveau.
Tous les hôtes KVM avec le même firmware
Sur un portable, ça coûte un peu de latence et un peu de batterie. Sur un hyperviseur, chaque invité le paie aussi, et rien de ce qui suit n’est dû à Proxmox. Tout se passe dans le code KVM du noyau Linux, donc c’est pareil sur n’importe quel hôte construit sur KVM, quoi qu’il y ait par-dessus : Proxmox VE, oVirt, OpenStack, ou QEMU et libvirt tout simplement11.
Ce que fait KVM quand l’hôte perd son TSC
KVM ne donne une horloge stable à ses invités que si l’hôte lui-même tourne sur le TSC. Dans le code source du noyau, l’horloge maître, une référence de temps partagée par tous les vCPU d’une VM, n’est activée que si host_tsc_clocksource est vrai12. Retombez sur le HPET et elle se désactive, et plusieurs choses changent d’un coup :
| Hôte sur TSC | Hôte sur HPET, TSC marqué instable | |
|---|---|---|
| Horloge maître KVM12 | active | inactive |
| Un vCPU passe sur un autre cœur12 | rien à mettre à jour | une mise à jour d’horloge forcée sur chaque vCPU de la VM, jusqu’à 10 par seconde |
| Le décalage TSC de l’invité12 | laissé tel quel | réécrit à chaque déplacement, et mis en mode rattrapage |
PVCLOCK_TSC_STABLE_BIT vers les invités12 | positionné | non positionné |
clock_gettime() dans l’invité13 | lu en espace utilisateur | un appel système, décidé une fois au démarrage de l’invité |
| Lectures d’horloge de l’invité entre vCPU14 | indépendantes | chaque lecture comparée à une seule valeur globale partagée |
La dernière ligne est celle qui passe mal à l’échelle. Sans le bit stable, le noyau invité ne peut pas compter sur l’accord entre l’horloge de chaque vCPU et celle des autres, donc chaque lecture est comparée à « last_value, a global accumulator » et y est inscrite par un compare-and-exchange atomique si elle est plus récente14. Chaque vCPU de l’invité écrit dans cette seule valeur. Plus il y a de vCPU qui lisent l’horloge, plus ils font la queue dessus.
Rien de tout ça n’apparaît comme une erreur. La clocksource de l’invité indique toujours kvm-clock, et rien dans l’invité ne vous dira pourquoi il a ralenti.
Ce que ça coûte, mesuré
Je l’ai mesuré sur ce portable plutôt que d’en rester à la théorie. La clocksource de l’hôte peut être changée à chaud, ce qui désactive l’horloge maître de KVM exactement comme le repli au démarrage. Un invité jetable a démarré le propre noyau de l’hôte avec 4 vCPU et un minuscule initramfs dont le seul programme chronomètre clock_gettime(). Deux passes dans chaque configuration, l’hôte d’abord, puis l’invité :
clock_gettime(), moyenne de deux passes dans chaque configuration. L’hôte devient 61 à 73 fois plus lent sur le HPET ; l’invité environ 7 fois avec un vCPU et 8 avec les quatre. Les panneaux ont des échelles différentes, parce que les barres HPET de l’hôte écraseraient celles de l’invité.L’invité ne paie pas directement le prix du HPET, parce que kvm-clock lit toujours le TSC en dessous. Ce qu’il perd, c’est la lecture en espace utilisateur et l’indépendance entre vCPU, et ça suffit à le rendre sept fois plus lent. Avec les quatre vCPU qui lisent en même temps, le coût monte encore, et c’est cette valeur globale partagée à l’œuvre. Un invité à 32 ou 64 vCPU fait davantage la queue dessus. Je ne l’ai pas mesuré.
Mettons un chiffre dessus. Un invité qui lit l’horloge un million de fois par seconde sur un vCPU y passe 2,8 % de ce vCPU sur un hôte sain, et 19 % sur celui-ci. Les bases de données, la Java Virtual Machine (JVM) et tout ce qui horodate son travail lisent beaucoup l’horloge.
Cette mesure sous-estime d’ailleurs le problème. Changer de clocksource à la main désactive l’horloge maître mais ne marque pas le TSC instable, donc les réécritures de décalage du tableau ci-dessus n’ont jamais eu lieu. Le repli au démarrage sur ce E595 le marque instable, et un hôte dans cet état paie les deux.
clockbench.c, le benchmark, à lancer sur l'hôte ou comme init d'un invitéVérifier un hôte, et les limites
J’ai expliqué pourquoi les horloges des invités sont fragiles au départ dans l’article sur ptp_kvm ; ici, c’est le même problème qui arrive par en dessous. Vérifiez l’hôte, pas l’invité :
journalctl -k -b | grep -iE 'TSC warp|TSC unstable'
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tscsync se lance depuis GRUB ou systemd-boot1, ce qui couvre la plupart des hôtes KVM. Proxmox VE utilise GRUB, ou systemd-boot sur une installation UEFI avec ZFS en racine et Secure Boot désactivé15. Soyez quand même honnête sur les limites avant que ça approche un nœud de production.
| Limite | Que faire |
|---|---|
| AMD Zen et Hygon seulement1 | les hôtes Intel avec TSC_ADJUST sont déjà corrigés par le noyau |
| Toutes les machines de sa liste de matériel sont des portables4 | considérez un serveur comme non testé et signalez-le |
| Le mode sync refuse le matériel hors de sa liste sauf si vous l’autorisez explicitement1 | lisez pourquoi avant de passer outre |
| Un premier démarrage sync peut se bloquer | mode mesure d’abord, console hors bande ouverte, un nœud à la fois |
| Une mise à jour du BIOS peut corriger le défaut | repassez en mode mesure ensuite et vérifiez1 |
| Une synchro propre peut quand même perdre le TSC à cause du watchdog | vérifiez current_clocksource après chaque démarrage ; tsc=nowatchdog si le watchdog est en cause, puis mesurez vous-même |
Un défaut de firmware qui coûte un peu de batterie à un portable coûte à un hyperviseur une part de chaque invité qu’il fait tourner, à chaque seconde où il est allumé, et les propriétaires des invités n’ont aucun moyen de le voir. C’est l’argument pour vérifier current_clocksource sur chaque hôte que vous exploitez, quel qu’en soit le fabricant.
tscsync README — ce qu’il fait, le coût d’une lecture d’horloge sur le HPET, pourquoi
tsc=reliablen’est pas une correction, le modèle de sécurité, et la règleUNSURE. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎Linux kernel — arch/x86/kernel/tsc_sync.c — la gestion de
TSC_ADJUST, le test de warp et le message « Measured %Ld cycles TSC warp between CPUs, turning off TSC clock. ». ↩︎ ↩︎ ↩︎tscsync — docs/DESIGN.md — AMD Zen n’a pas d’
IA32_TSC_ADJUST,tsc=directsyncrejeté en amont, Windows écrit lui-même les TSC au démarrage, et le warp qui suit le temps passé dans le firmware sur le Legion Pro 5. ↩︎ ↩︎ ↩︎tscsync — docs/HARDWARE.md — les machines testées, les cœurs de l’IdeaPad 5 acceptés à -28 à -42 cycles, et les corrections de BIOS signalées sur les A485, E495 et T14s Gen 1. ↩︎ ↩︎ ↩︎ ↩︎
Lenovo — BIOS Update Utility readme, ThinkPad E495/E595, version 1.30 — les deux modèles dans un seul paquet, l’historique des versions jusqu’à la 1.30 (14 mai 2024), et sa liste de changements. ↩︎ ↩︎
Linux kernel — kernel/time/clocksource.c — le watchdog des clocksources, sa comparaison d’intervalles et le message « Marking clocksource %s unstable due to frequency skew ». ↩︎
Linux kernel — kernel-parameters —
tsc=reliabledésactive la vérification à l’exécution et les contrôles de stabilité au démarrage ;tsc=nowatchdogne désactive que le watchdog des clocksources. ↩︎tscsync issue #1 — Lenovo ThinkPad E595 — les rapports 1.28 et 1.30, l’analyse du watchdog et les changements suggérés. ↩︎
Linux kernel — Real Time Clock (RTC) Drivers for Linux — « The clock can also be set to generate an interrupt upon every clock update, thus generating a 1Hz signal », lu via
/dev/rtc. ↩︎chrony — chronyc manual —
tracking: « The ‘frequency’ is the rate by which the system’s clock would be wrong if chronyd was not correcting it. » ↩︎KVM — Management Tools — Proxmox VE, oVirt, OpenStack et les outils basés sur libvirt, qui gèrent tous KVM. ↩︎
Linux kernel — arch/x86/kvm/x86.c —
pvclock_update_vm_gtod_copy(): « If the host uses TSC clock, then passthrough TSC as stable to the guest », etPVCLOCK_TSC_STABLE_BITpositionné seulement avec l’horloge maître. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎Linux kernel — arch/x86/kernel/kvmclock.c — l’invité n’active l’horloge en espace utilisateur
VDSO_CLOCKMODE_PVCLOCKque siPVCLOCK_TSC_STABLE_BITest positionné. ↩︎Linux kernel — arch/x86/kernel/pvclock.c — sans
PVCLOCK_TSC_STABLE_BIT, chaque lecture est comparée à « last_value, a global accumulator » avec un compare-and-exchange atomique. ↩︎ ↩︎Proxmox VE — Host Bootloader — GRUB par défaut, systemd-boot pour les installations UEFI avec ZFS en racine et Secure Boot désactivé. ↩︎