Update, 11. Oktober 2026, 16:30: Ich habe inzwischen BIOS 1.30 geflasht. Es behebt den Versatz nicht. tscsync bringt die Kerne immer noch in eine Linie, aber an zwei Sync-Boots hat der Kernel den TSC trotzdem fallen lassen, diesmal über seinen Clocksource-Watchdog statt über die Warp-Prüfung: BIOS 1.30: Der Sync hält, der TSC geht trotzdem. Mit zusätzlich
tsc=nowatchdoghielt der TSC, und fünf Minuten Messung gegen die Echtzeituhr fanden keine Warps und keine Drift: tsc=nowatchdog: Fünf Minuten, keine Warps. Ein Boot ist keine Reihe von Boots, deshalb nenne ich das hält, nicht behoben.
Jeder Boot, dieselbe Warnung
Das stand bei jedem einzelnen Boot im Kernel-Log meines ThinkPad E595:
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
Nichts ist abgestürzt. Auf dem Desktop ist nichts aufgegangen. Die Maschine hat ihre ganze Uhr ab dem Booten still auf einem langsameren Timer-Chip laufen lassen, und das hätte sie ewig so weitergemacht, wenn ich das Journal nicht wegen etwas anderem gelesen hätte.
Der Time Stamp Counter (TSC) ist ein Zähler in jedem CPU-Kern, der mit fester Rate tickt. Ihn zu lesen ist ein einziger Befehl. Linux nutzt ihn als Hauptuhr, aber nur, wenn alle Kerne beim Zählerstand übereinstimmen1. Hängt ein Kern hinterher, kann der Kernel ihm nicht trauen und fällt auf den High Precision Event Timer (HPET) zurück, ein eigenes Timer-Gerät. Der Autor von tscsync hat ein Uhrlesen auf HPET mit etwa 1,2 µs gemessen, gegen einige zehn Nanosekunden auf dem TSC, und Kernel und Desktop lesen die Uhr zehntausende Male pro Sekunde1. Das ist Latenz und Akku, bei jedem Boot bezahlt, für nichts.
Wie weit hing der Kern also hinterher? Über drei Sekunden. Auf einem Laptop mit 2,1 GHz sind das 6,6 Milliarden Takte.
Was die Firmware übergibt
Die Meldung nennt CPU 0 und CPU 2, aber der Fehler liegt nicht bei CPU 2. Von tscsync aus dem Bootloader gemessen und vom Kernel, sobald er startet, ist das Bild jedes Mal dasselbe:
| Kern | Versatz zu CPU 0 | Was das bedeutet |
|---|---|---|
| CPU 0, der Boot-Prozessor (BSP) | 0 | die Referenz, und der Ausreißer |
| CPUs 1 bis 7, die anderen Kerne (APs) | etwa +6,2 bis +6,8 Milliarden Takte | untereinander auf 32 Takte einig |
| Abstand bei 2.096 MHz | 2,9 bis 3,3 Sekunden | der Boot-Prozessor hängt hinterher, bei jedem Boot |
Sieben Kerne sind sich einig. Einer ist drei Sekunden zu spät. Die Firmware fährt den Boot-Prozessor hoch, erledigt ihre Arbeit auf ihm und übergibt an den Bootloader, ohne diesen einen Zähler je wieder mit dem Rest in eine Linie zu bringen.
Warum kann Linux das nicht einfach reparieren? Auf Intel-CPUs gibt es ein Register, IA32_TSC_ADJUST, mit dem der Kernel den Zähler eines Kerns nachstellen kann, und der Sync-Code des Kernels nutzt es, wenn es da ist2. AMD-Zen-CPUs haben es nicht. Ohne es kann nichts in Linux den Versatz reparieren, und der Versuch, die Zähler im Kernel direkt zu schreiben (tsc=directsync), wurde upstream als unzuverlässig abgelehnt3.
Also tut der Kernel das einzig Ehrliche, das ihm bleibt. Er misst, findet zwischen zwei Kernen rückwärts laufende Zeit, gibt die Warnung aus und hört auf, den TSC zu nutzen2.
Vierzig Boots, vierzig Warps
Ein Boot ist eine Anekdote. Das Journal auf dieser Maschine reicht bis zum 30. Mai 2026 zurück, also habe ich es gefragt.
| Boots | Warp, Takte | Warp bei 2.096 MHz | |
|---|---|---|---|
| 30. Mai bis 11. Oktober 2026 | 40 von 40 | 6,16 bis 9,88 Milliarden | 2,94 bis 4,72 s |
| Median | 6,57 Milliarden | 3,14 s | |
| Nach komplettem Ausschalten für etwa 27 s | 1 | 6,60 Milliarden | 3,15 s |
| Mit tscsync im Sync-Modus, BIOS 1.28 | 1 | keiner protokolliert | TSC behalten |
| BIOS 1.30, tscsync aus | 1 | 6,13 Milliarden | 2,92 s |
| BIOS 1.30, tscsync im Sync-Modus | 2 | keiner protokolliert | TSC vom Watchdog fallen gelassen, siehe unten |
BIOS 1.30, Sync-Modus und tsc=nowatchdog | 1 | keiner protokolliert | TSC behalten |
Jeder Boot. Kaltstart, Neustart, nach einem Ausschalten, das lang genug war, dass man erwarten würde, alles Abgestandene sei weg. Der Betrag schwankt, und das passt zu dem, was der Autor von tscsync selbst an einem Lenovo Legion gefunden hat: Dort skaliert der Warp fast perfekt damit, wie lange die Maschine in der Firmware verbringt, bevor der Bootloader startet3. Ein Boot, der länger auf dem Logo steht, kommt weiter hinten heraus.
Der verlockende Fix ist tsc=reliable auf der Kernel-Kommandozeile, was Linux sagt, es solle aufhören zu prüfen. Lass es. Die Kerne liegen wirklich drei Sekunden auseinander, und die Prüfung abzuschalten ändert daran nichts; sie lässt den Kernel nur eine Uhr benutzen, die rückwärts springt, wenn ein Task zwischen Kernen wandert. Das README von tscsync sagt dasselbe und nennt das Stottern bei Audio und Video, das darauf folgt1.
Darf ich fragen, warum das immer noch ausgeliefert wird?
Ich frage nicht, wer. Ich frage, was in Lenovos Ablauf ein BIOS hat rausgehen lassen, das bei jedem Boot einen Kern drei Sekunden zurücklässt.
Die Antwort steht in den Design-Notizen von tscsync, und sie ist nicht schmeichelhaft. Fisher-Price OS (Windows) schreibt die TSCs beim Booten selbst3. Ein Laptop, der nur darauf validiert wird, besteht, weil das Betriebssystem still hinter der Firmware aufräumt. Linux lässt seine Sicherheitsprüfung an und meldet, was es bekommen hat, und genau deshalb zeigt sich der Fehler dort und nirgends sonst.
Das Unangenehme ist, dass das angeblich schon einmal behoben wurde. Nutzer berichten, Lenovo habe dieselbe Klasse von Fehler per BIOS-Update beim ThinkPad A485, beim T14s Gen 1 AMD und beim E495 behoben4. Das E495 ist das 14-Zoll-Geschwister dieser Maschine, und beide teilen sich ein BIOS: Lenovos eigenes Readme zum Paket führt beide auf5.
Mein E595 lief mit 1.28 und hatte bei jedem Boot einen Warp. Lenovo hat seitdem 1.29 und 1.30 veröffentlicht, und keine der beiden Änderungslisten sagt ein Wort zum TSC; 1.30 behebt „system randomly freeze since BIOS V1.27“ und einige Sicherheitsprobleme5. Also habe ich 1.30 aufgespielt und neu gemessen. Der erste Boot darauf, mit abgeschaltetem tscsync, protokollierte einen Warp von 6.126.446.316 Takten: 2,92 Sekunden, CPU#0 -> CPU#2, wie immer. 1.30 behebt es nicht. Die beiden Modelle teilen sich ein BIOS, wenn der Bericht zum E495 also stimmte, wäre diese Maschine auch repariert. Der Bericht nennt keine BIOS-Version und verlinkt keine Logs4.
Wird Lenovo für einen Fehler, den das Betriebssystem, auf dem sie testen, vor ihnen versteckt, zu einem Laptop von 2019 zurückgehen? Nein.
tscsync, zuerst gemessen
tscsync ist ein kleines UEFI-Programm, das aus GRUB oder systemd-boot kurz vor Linux läuft. Es fragt jeden Kern über die Multiprozessor-Dienste der Firmware selbst nach seinem TSC, rechnet den Versatz jedes Kerns zum Boot-Prozessor aus, schiebt die nachhängenden Zähler vor, bis sie übereinstimmen, und prüft das Ergebnis dann mit einem Test nach dem Vorbild der Warp-Prüfung des Kernels1. Es schreibt nie Flash, NVRAM oder Firmware-Einstellungen; die Zähler, die es ändert, werden beim Ausschalten zurückgesetzt1.
Das ist eine Menge, die man einem Bootloader überlässt, deshalb habe ich zuerst den Code gelesen, bei Commit 230eaa9, bevor ich es gebaut habe. Die Installation lief dann so, wie das Projekt es verlangt:
| Schritt | Befehl | Was zurückkam |
|---|---|---|
| Bauen | make | Binärdateien für GRUB und systemd-boot |
| Installieren, nur lesend | sudo scripts/install.sh | Messmodus, nichts in die Zähler geschrieben |
| Ausschalten, booten, nachsehen | tscsync-status | CPU 0 2.964 ms hinter allen 7 APs, die APs auf Mikrosekunden einig |
| Einschalten | sudo scripts/set-mode.sh sync | wirkt ab dem nächsten Boot |
Der Messmodus ist der Teil, der in jedes Werkzeug dieser Art übernommen gehört. Er zeigt dir den Fehler auf deiner eigenen Hardware, bevor er irgendetwas ändern darf, und ein Hänger-Schutz schaltet ihn ab, wenn ein Boot Linux nie erreicht1.
Der erste Boot im Sync-Modus, direkt aus 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
Und der Kernel, eine Sekunde später:
clocksource: Switched to clocksource tsc-early
tsc: Refined TSC clocksource calibration: 2096.066 MHz
clocksource: Switched to clocksource tsc
Keine Warp-Zeile. Kein „unstable“. /sys/devices/system/clocksource/clocksource0/current_clocksource liest tsc. 121 ms Bootzeit für die Uhr, die die Hardware von Anfang an haben sollte. Das war auf BIOS 1.28. 1.30 war nicht so ordentlich.
Die 21 Nanosekunden, die Linux überstimmt hat
Eine Zeile in diesem Bericht sieht schlimmer aus, als sie ist, und es lohnt sich, sie zu erklären, statt sie zu verstecken. Der abschließende Prüfdurchlauf von tscsync hat einen Kern markiert:
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 Takte sind etwa 21 Nanosekunden. tscsync zielt auf Übereinstimmung innerhalb von etwa 10 Takten1, nach seinem eigenen Ziel hat CPU 7 es also verfehlt, und das hat es auch gesagt. Dann bootete Linux und fiel überhaupt nicht auf HPET zurück.
Die beiden Prüfungen stellen verschiedene Fragen:
| Versatz-Urteil von tscsync | Warp-Test des Kernels | |
|---|---|---|
| Frage | wie weit liegen die Zähler auseinander? | läuft die Zeit zwischen Kernen je rückwärts? |
| Methode | Schätzung aus einem zeitgemessenen Hin und Zurück | Kerne lesen abwechselnd, Vergleich mit dem letzten Wert2 |
| Bestehensgrenze | etwa 10 Takte | kein Wert niedriger als der davor |
| CPU 7 bei diesem Boot | 45 Takte, BAD | kein Warp, TSC behalten |
Das warps bsp/ap 0/0 in derselben Zeile ist tscsync, das den Test im Stil des Kernels laufen lässt. Es hat keinen gesehen. Der Kernel auch nicht.
Das Projekt weiß bereits, dass sein Urteil strenger sein kann als das des Kernels. Ein IdeaPad in seiner Hardwareliste hatte vier Kerne mit 28 bis 42 Takten markiert, die der Kernel annahm, und seit 2.1 wird ein langsames Hin und Zurück als UNSURE statt BAD gemeldet4. Die Regel des README für diesen Fall ist die richtige: Die eigene Prüfung des Kernels entscheidet, und wenn die Clocksource tsc ist, hat sie bestanden1. Ein Hin und Zurück von 357 Takten ist auf dieser Maschine nicht langsam, also kam stattdessen BAD heraus. Ein Werkzeug, das seinen Rechenweg zeigt und dann dem Kernel das letzte Wort lässt, ist richtig herum gebaut.
BIOS 1.30: Der Sync hält, der TSC geht trotzdem
Auf 1.30 habe ich tscsync wieder in den Sync-Modus gesetzt und zweimal neu gestartet. Beide Male hat es seine Arbeit getan. Es schob den Boot-Prozessor um etwa 6,17 Milliarden Takte vor, jeder Kern kam innerhalb weniger Dutzend Takte heraus, und der Kernel protokollierte keinen Warp. Beide Male landete Linux ein paar Sekunden später trotzdem auf HPET:
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
Das ist eine andere Prüfung. Der Warp-Test läuft, während jeder Kern hochkommt, und fragt, ob die Zeit zwischen Kernen je rückwärts läuft. Der Clocksource-Watchdog läuft nach dem Booten weiter. Etwa jede halbe Sekunde vergleicht er, wie weit sich der TSC bewegt hat, mit dem, wie weit sich HPET bewegt hat, und wenn sie um mehr als einen kleinen Spielraum abweichen, markiert er den TSC als instabil6. Einmal markiert, bleibt er markiert bis zum nächsten Boot.
tsc=nowatchdog überspringt nur die zweite, tsc=reliable überspringt beide und würde einen echten Warp verdecken.| Boot auf 1.30 | Warp-Prüfung | HPET-Intervall | TSC-Intervall | TSC gegen HPET |
|---|---|---|---|---|
| Erster Sync-Boot | bestanden | 495.426.793 ns | 495.006.210 ns | etwa 850 ppm zu langsam |
| Zweiter Sync-Boot | bestanden | 495.136.113 ns | 496.026.671 ns | etwa 1.800 ppm zu schnell |
Läuft der TSC also mit der falschen Rate? Nein. Bei laufender Maschine habe ich den TSC 30 Sekunden lang in 5-Sekunden-Fenstern gegen die HPET-basierte Rohuhr des Kernels gemessen. Jedes Fenster kam auf 2.096,062 MHz, etwa 4 Millionstel neben der eigenen Kalibrierung des Kernels. Ein echter Ratenfehler schwankt nicht zwischen zwei Boots von zu langsam zu zu schnell und verschwindet dann. Irgendwas stört früh im Boot ein Halbsekundenfenster, und die Zeile remote CPU 5 read timed out direkt davor deutet auf einen Kern, der kurz nicht antwortet. Firmware, die den CPUs Zeit stiehlt, würde passen. Bewiesen habe ich es nicht.
Zwei Boots sind alles, was es an Belegen gibt. Ich kann noch nicht sagen, ob es 1.30 ist, Zufall oder etwas, das der Schreibvorgang von tscsync auslöst. Der eine Sync-Boot auf 1.28 behielt den TSC, die zwei auf 1.30 verloren ihn, und das reicht nicht, um dem BIOS die Schuld zu geben.
Für diesen Fall gibt es eine engere Kernel-Option als tsc=reliable. tsc=nowatchdog schaltet den Clocksource-Watchdog für den TSC ab und lässt die Warp-Prüfung beim Booten in Ruhe7. Sollte tscsync je mit dem Abgleich scheitern, würde der Kernel den Warp immer noch erwischen und auf HPET zurückfallen. Das war der nächste Test. Die Zahlen und ein paar vorgeschlagene Änderungen an der Berichterstattung von tscsync selbst liegen im Issue-Tracker des Projekts8.
tsc=nowatchdog: Fünf Minuten, keine Warps
Ich habe tsc=nowatchdog hinzugefügt und neu gestartet. tscsync schob den Boot-Prozessor um 6.207.104.421 Takte vor, der Kernel protokollierte keinen Warp und keinen Skew, und current_clocksource las tsc. Der erste Boot auf 1.30, der ihn behielt.
Das beweist nur, dass der Watchdog den TSC fallen gelassen hatte. Es beweist nicht, dass man sicher auf dem TSC laufen kann, und mit abgeschaltetem Watchdog passt im Kernel niemand mehr auf. Also habe ich selbst aufgepasst.
Meine frühere Ratenprüfung half hier nicht. Sie maß den TSC gegen CLOCK_MONOTONIC_RAW, was in Ordnung war, solange der Kernel auf HPET lief. Auf dem TSC wird diese Uhr aus dem TSC gebildet, der Test würde den Zähler also mit sich selbst vergleichen. HPET lässt sich aus dem User Space auch nicht lesen, weil der Kernel von Fedora ohne CONFIG_HPET_MMAP gebaut ist. Die Echtzeituhr (RTC) schon. Sie läuft von ihrem eigenen Quarz, und der Kernel weckt ein Programm bei jedem Tick ihres Sekundenzählers9.
Also habe ich eine kleine Prüfung geschrieben, tsccheck. Sie pinnt sich auf CPU 0 und lässt alle 15 Sekunden vier Tests laufen:
| Prüfung | Wie | Wie ein Fehler aussieht |
|---|---|---|
| Rate | TSC-Takte zwischen Sekunden-Ticks der RTC gezählt | die MHz driften in eine Richtung |
| Versatz | 3.000 Hin und Zurück von CPU 0 zu jedem Kern, Versatz aus dem schnellsten | ein Versatz, der wächst |
| Warps | bei jedem Hin und Zurück: Hat ein Kern einen niedrigeren Wert gelesen als einen schon gesehenen? | alles über null |
| Clocksource | current_clocksource lesen | alles außer tsc |
Das Hin und Zurück ist der Kern des Ganzen. CPU 0 liest ihren Zähler und reicht ihn hinüber, der andere Kern liest seinen eigenen und reicht ihn zurück, und CPU 0 liest noch einmal. Wenn die Kerne übereinstimmen, muss der mittlere Wert zwischen den beiden anderen landen:
Die Schleife, die das macht:
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));
}
}
Das ganze Programm ist schlichtes C für x86-64-Linux, ohne irgendetwas, das an tscsync oder diesen Laptop gebunden ist. Es braucht Root für /dev/rtc0, und das dritte Argument ist der Wert deines eigenen Kernels aus journalctl -k -b | grep 'Refined TSC':
gcc -O2 -Wall -pthread -o tsccheck tsccheck.c
sudo ./tsccheck 15 20 2096.073
SHA-256 02eba1966a0097196acd5dfdcad9c4b315cafc1854c60f34c5b56080e6c38408.
Zwanzig Messungen über fünf Minuten, direkt nach dem Booten:
| Messgröße | Ergebnis |
|---|---|
| Clocksource | tsc in allen 20 Messungen, keine Kernel-Meldungen |
| Warps | 0 in etwa 420.000 Hin und Zurück |
| Größter Versatz zu CPU 0 | 85 Takte, etwa 41 ns, kein Trend |
| Rate über den ganzen Lauf | 2.096,027 MHz, ab der ersten Minute innerhalb von 2.096,022 bis 2.096,031 |
| Rate je 15-Sekunden-Fenster | 2.095,943 bis 2.096,104 MHz |
Die Fenster sehen schlimmer aus, als sie sind. Ein später RTC-Tick macht ein Fenster lang und das nächste kurz, die Fehler kommen also in Paaren, die sich aufheben: 61 Millionstel (ppm) zu langsam bei 30 Sekunden, 15 ppm zu schnell bei 60. Drift hebt sich nicht selbst auf. Der Wert über den ganzen Lauf hat sich kaum bewegt.
Der Wert über den ganzen Lauf liegt die vollen fünf Minuten 22 ppm unter den 2.096,073 MHz des Kernels. Das ist der Abstand zwischen zwei Quarzen, und er blieb stehen. chrony, das die Uhr gegen Zeitserver im Internet nachführt, hatte sie bei 16 bis 19 ppm zu langsam10. Dieselbe Richtung, dieselbe Größe.
Die Versätze springen zwischen denselben paar Werten, 10, 21 und 33 Takte in beide Richtungen, ohne dass ein Kern davonläuft. Das ist Rauschen aus einem Hin und Zurück von etwa 550 Takten. Die Prüfung von tscsync würde einige davon immer noch BAD nennen. Der Kernel nicht.
Hat der TSC gehalten? Ja, bei jeder Messgröße, die ich hatte. Fünf Minuten nach einem Boot sind trotzdem keine Reihe von Boots. Als Nächstes kommt dieselbe Prüfung nach langer Laufzeit und noch einmal über ein paar weitere Neustarts.
Der Fix gehört in die Firmware
tscsync sollte nicht existieren müssen. Sein eigenes README sagt das: Firmware-Hersteller dürfen den Ansatz und den Code gern übernehmen, „which is where this should be fixed“1. Lenovo hat den Quellcode. Der Autor von tscsync hatte nur das, was sein Laptop ihm verraten wollte, und 80 Boots an Logs1.
Das ist der Teil, der hängen bleibt. Für den Fix musste ein Fremder von außerhalb der Firma messen, diagnostizieren und drumherum arbeiten, während die Firma, die die Hardware verkauft hat, sie gegen das eine Betriebssystem validiert hat, das den Fehler versteckt. Eine Maschine, die du nicht reparieren kannst, weil ihre Firmware geschlossen ist, gehört dir nicht wirklich, sie ist eine Leihgabe mit Auflagen. Offene Firmware mit signierten Updates hätte das einmal richtig beheben lassen, von jedem, dem es wichtig genug war, hinzusehen.
Bis dahin: gute Arbeit vom Autor von tscsync. Zuerst der Messmodus, ein Hänger-Schutz, die Zahlen offen auf dem Tisch und ein ehrliches Urteil, auch wenn es dem Kernel widerspricht. So sollten Low-Level-Werkzeuge gebaut werden.
Jeder KVM-Host mit derselben Firmware
Auf einem Laptop kostet das etwas Latenz und etwas Akku. Auf einem Hypervisor zahlt jeder Gast mit, und nichts von dem, was folgt, geht auf Proxmox zurück. Es lebt alles im KVM-Code des Linux-Kernels, ist also auf jedem Host auf KVM-Basis gleich, was auch immer obendrauf sitzt: Proxmox VE, oVirt, OpenStack oder schlichtes QEMU mit libvirt11.
Was KVM tut, wenn der Host seinen TSC verliert
KVM gibt seinen Gästen nur dann eine stabile Uhr, wenn der Host selbst auf dem TSC läuft. Im Kernel-Quellcode wird die Master Clock, eine gemeinsame Zeitreferenz für jede vCPU einer VM, nur eingeschaltet, wenn host_tsc_clocksource wahr ist12. Fällt der Host auf HPET zurück, schaltet sie ab, und mehreres ändert sich auf einmal:
| Host auf TSC | Host auf HPET, TSC als instabil markiert | |
|---|---|---|
| KVM-Master-Clock12 | an | aus |
| Eine vCPU wandert auf einen anderen Kern12 | nichts zu aktualisieren | ein Uhr-Update für jede vCPU dieser VM erzwungen, bis zu 10 pro Sekunde |
| Der TSC-Versatz des Gasts12 | bleibt in Ruhe | bei jedem Wandern neu geschrieben und in den Aufholmodus gesetzt |
PVCLOCK_TSC_STABLE_BIT für Gäste12 | gesetzt | nicht gesetzt |
clock_gettime() im Gast13 | im User Space gelesen | ein Systemaufruf, einmal beim Booten des Gasts entschieden |
| Uhrlesen im Gast über vCPUs hinweg14 | unabhängig | jedes Lesen gegen einen gemeinsamen globalen Wert geprüft |
Die letzte Zeile ist die, die schlecht skaliert. Ohne das Stable-Bit kann der Gast-Kernel nicht darauf vertrauen, dass die Uhr jeder vCPU mit den anderen übereinstimmt, also wird jedes Lesen mit „last_value, a global accumulator“ verglichen und per atomarem Compare-and-Exchange eingetauscht, wenn es neuer ist14. Jede vCPU im Gast schreibt auf diesen einen Wert. Je mehr vCPUs die Uhr lesen, desto mehr stehen sie daran an.
Nichts davon zeigt sich als Fehler. Die Clocksource des Gasts liest weiter kvm-clock, und nichts im Gast sagt dir, warum er langsamer geworden ist.
Was es kostet, gemessen
Ich habe es auf diesem Laptop gemessen, statt es als Theorie stehen zu lassen. Die Clocksource des Hosts lässt sich im laufenden Betrieb umschalten, was die Master Clock von KVM genauso abschaltet wie der Rückfall beim Booten. Ein Wegwerf-Gast bootete den eigenen Kernel des Hosts mit 4 vCPUs und einer winzigen Initramfs, deren einziges Programm clock_gettime() stoppt. Zwei Läufe je Variante, erst Host, dann Gast:
clock_gettime(), Mittel aus zwei Läufen je Variante. Der Host wird auf HPET 61- bis 73-mal langsamer; der Gast etwa 7-mal mit einer vCPU und 8-mal mit allen vier. Die Felder haben verschiedene Skalen, weil die HPET-Balken des Hosts die des Gasts plattdrücken würden.Der Gast zahlt den Preis von HPET nicht direkt, weil kvm-clock darunter immer noch den TSC liest. Was er verliert, ist das Lesen im User Space und die Unabhängigkeit zwischen den vCPUs, und das allein ist siebenmal langsamer. Lesen alle vier vCPUs gleichzeitig, steigen die Kosten noch einmal, und das ist dieser gemeinsame globale Wert bei der Arbeit. Ein Gast mit 32 oder 64 vCPUs steht stärker daran an. Gemessen habe ich das nicht.
Mach eine Zahl daraus. Ein Gast, der die Uhr eine Million Mal pro Sekunde auf einer vCPU liest, verbringt auf einem gesunden Host 2,8 % dieser vCPU damit und auf diesem 19 %. Datenbanken, die Java Virtual Machine (JVM) und alles andere, was seine Arbeit mit Zeitstempeln versieht, lesen die Uhr viel.
Dieser Lauf unterschätzt es außerdem. Die Clocksource von Hand umzuschalten schaltet die Master Clock ab, markiert den TSC aber nicht als instabil, die Neuschreibungen des Versatzes aus der Tabelle oben liefen also nie. Der Rückfall beim Booten auf diesem E595 markiert ihn als instabil, und ein Host in diesem Zustand zahlt beides.
clockbench.c, der Benchmark, auf dem Host oder als Init eines GastsEinen Host prüfen, und die Grenzen
Warum Gast-Uhren überhaupt fragil sind, habe ich im Beitrag zu ptp_kvm behandelt; das hier ist dasselbe Problem, das von unten kommt. Prüf den Host, nicht den Gast:
journalctl -k -b | grep -iE 'TSC warp|TSC unstable'
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tscsync läuft aus GRUB oder systemd-boot1, was die meisten KVM-Hosts abdeckt. Proxmox VE nutzt GRUB, oder systemd-boot bei einer UEFI-Installation mit ZFS als Root und abgeschaltetem Secure Boot15. Sei aber ehrlich bei den Grenzen, bevor es in die Nähe eines Produktionsknotens kommt.
| Grenze | Was du dagegen tust |
|---|---|
| Nur AMD Zen und Hygon1 | Intel-Hosts mit TSC_ADJUST repariert der Kernel schon |
| Jede Maschine in seiner Hardwareliste ist ein Laptop4 | behandle einen Server als ungetestet und melde ihn |
| Der Sync-Modus verweigert Hardware außerhalb seiner Liste, außer du stimmst zu1 | lies nach, warum, bevor du das übergehst |
| Ein erster Sync-Boot kann hängen | zuerst der Messmodus, Out-of-Band-Konsole offen, ein Knoten nach dem anderen |
| Ein BIOS-Update behebt den Fehler vielleicht | danach zurück auf Messmodus stellen und prüfen1 |
| Ein sauberer Sync kann den TSC trotzdem an den Watchdog verlieren | nach jedem Boot current_clocksource prüfen; tsc=nowatchdog, wenn der Watchdog die Ursache ist, und dann selbst messen |
Ein Firmware-Fehler, der einen Laptop etwas Akku kostet, kostet einen Hypervisor ein Stück jedes Gasts, den er betreibt, jede Sekunde, die er läuft, und die Besitzer der Gäste haben keine Möglichkeit, das zu sehen. Das ist das Argument dafür, current_clocksource auf jedem Host zu prüfen, den du betreibst, egal wer ihn gebaut hat.
tscsync README — was es tut, die Kosten eines Uhrlesens auf HPET, warum
tsc=reliablekein Fix ist, das Sicherheitsmodell und die Regel zuUNSURE. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎Linux kernel — arch/x86/kernel/tsc_sync.c — die Behandlung von
TSC_ADJUST, der Warp-Test und die Meldung „Measured %Ld cycles TSC warp between CPUs, turning off TSC clock.“ ↩︎ ↩︎ ↩︎tscsync — docs/DESIGN.md — AMD Zen hat kein
IA32_TSC_ADJUST,tsc=directsyncupstream abgelehnt, Windows schreibt die TSCs beim Booten selbst, und der Warp skaliert auf dem Legion Pro 5 mit der Zeit in der Firmware. ↩︎ ↩︎ ↩︎tscsync — docs/HARDWARE.md — getestete Maschinen, die IdeaPad-5-Kerne, die bei -28 bis -42 Takten angenommen wurden, und die gemeldeten BIOS-Fixes beim A485, E495 und T14s Gen 1. ↩︎ ↩︎ ↩︎ ↩︎
Lenovo — BIOS Update Utility readme, ThinkPad E495/E595, version 1.30 — beide Modelle in einem Paket, die Versionsgeschichte bis 1.30 (14. Mai 2024) und deren Änderungsliste. ↩︎ ↩︎
Linux kernel — kernel/time/clocksource.c — der Clocksource-Watchdog, sein Intervallvergleich und die Meldung „Marking clocksource %s unstable due to frequency skew“. ↩︎
Linux kernel — kernel-parameters —
tsc=reliableschaltet die Prüfung zur Laufzeit und die Stabilitätsprüfungen beim Booten ab;tsc=nowatchdogschaltet nur den Clocksource-Watchdog ab. ↩︎tscsync issue #1 — Lenovo ThinkPad E595 — die Berichte zu 1.28 und 1.30, die Analyse des Watchdogs und die vorgeschlagenen Änderungen. ↩︎
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“, gelesen über
/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 und die Werkzeuge auf Basis von libvirt, die alle KVM verwalten. ↩︎
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“, undPVCLOCK_TSC_STABLE_BITnur mit der Master Clock gesetzt. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎Linux kernel — arch/x86/kernel/kvmclock.c — der Gast schaltet die User-Space-Uhr
VDSO_CLOCKMODE_PVCLOCKnur ein, wennPVCLOCK_TSC_STABLE_BITgesetzt ist. ↩︎Linux kernel — arch/x86/kernel/pvclock.c — ohne
PVCLOCK_TSC_STABLE_BITwird jedes Lesen gegen „last_value, a global accumulator“ mit einem atomaren Compare-and-Exchange geprüft. ↩︎ ↩︎Proxmox VE — Host Bootloader — standardmäßig GRUB, systemd-boot bei UEFI-Installationen mit ZFS-Root und abgeschaltetem Secure Boot. ↩︎