zwei Uhren, die niemals verwechselt werden sollten
Der Linux-Kernel führt nicht eine, sondern mehrere Uhren gleichzeitig, und die Wahl der falschen Uhr für die falsche Aufgabe führt zu Bugs, die nur bei einer NTP-Korrektur oder einem manuellen Zeitsprung sichtbar werden. Dieser Leitfaden erklärt den Unterschied zwischen CLOCK_REALTIME und CLOCK_MONOTONIC, warum Timeouts und Retry-Logik die Wall Clock meiden sollten und wie Skripte robust gegen Zeitsprünge bleiben.
Inhaltsverzeichnis
- 1. Zwei Uhren im Kernel: CLOCK_REALTIME und CLOCK_MONOTONIC
- 2. Warum sich die Wall Clock rückwärts bewegen kann
- 3. Monotonic Clock in der Praxis: uptime und clock_gettime
- 4. Auswirkungen auf Timeouts und Retry-Logik
- 5. CLOCK_MONOTONIC_RAW und CLOCK_BOOTTIME: die Feinheiten
- 6. Bash-Skripte robust gegen Zeitsprünge machen
- 7. Log-Zeitstempel: Wall Clock nötig, aber Vorsicht bei Deltas
- 8. systemd-Timer und die interne Monotonic-Logik
- 9. Clock-Typen im Überblick
- 10. Zusammenfassung
- 11. FAQ
1. Zwei Uhren im Kernel: CLOCK_REALTIME und CLOCK_MONOTONIC
Der Linux-Kernel stellt Anwendungen nicht nur eine, sondern mehrere unterschiedliche Uhren zur Verfügung, jede mit einer eigenen Garantie und einem eigenen Einsatzzweck. CLOCK_REALTIME, gemeinhin als Wall Clock bezeichnet, liefert die tatsächliche Kalenderzeit, so wie sie ein Mensch von einer Uhr abliest, inklusive Datum und Uhrzeit. CLOCK_MONOTONIC, die Monotonic Clock, liefert dagegen keine Kalenderzeit, sondern lediglich einen fortlaufend steigenden Zählerwert seit einem beliebigen, nicht spezifizierten Startpunkt, meist dem Systemstart.
Der entscheidende Unterschied liegt in der Garantie: Die Wall Clock kann sich jederzeit ändern, vorwärts wie rückwärts, etwa durch eine NTP-Korrektur, eine manuelle Anpassung durch einen Administrator oder eine Zeitzonenänderung. Die Monotonic Clock dagegen garantiert per Definition, niemals rückwärts zu laufen, unabhängig davon, was mit der Systemzeit sonst passiert. Diese Garantie macht die Monotonic Clock zum einzig korrekten Werkzeug für alles, was Zeitdifferenzen oder Laufzeiten misst, während die Wall Clock ausschließlich für die Anzeige und Speicherung von Kalenderzeitpunkten zuständig bleibt.
In der Praxis vermischen viele Entwickler beide Konzepte, oft ohne es zu bemerken, weil date +%s und ähnliche Befehle standardmäßig die Wall Clock liefern. Wer damit Laufzeiten misst, baut ein Skript, das unter normalen Umständen korrekt funktioniert, aber genau dann fehlschlägt, wenn eine NTP-Korrektur während der Messung die Systemzeit verändert. Das Verständnis von Monotonic Clock vs. Wall Clock ist deshalb kein akademisches Detail, sondern eine praktische Voraussetzung für robuste Systemprogrammierung.
2. Warum sich die Wall Clock rückwärts bewegen kann
Ein frisch gestarteter Server hat oft eine ungenaue Systemzeit, bis der NTP-Client, etwa chrony oder systemd-timesyncd, die erste Synchronisation mit einem Zeitserver abgeschlossen hat. Diese erste Korrektur kann die Systemzeit um Sekunden oder sogar Minuten nach vorne oder nach hinten verschieben, abhängig davon, wie stark die interne Hardware-Uhr vom tatsächlichen Zeitpunkt abweicht. Ein Prozess, der genau während dieser Korrektur die Wall Clock zur Zeitmessung nutzt, sieht in seltenen Fällen eine Zeitdifferenz, die negativ ist, obwohl real Zeit vergangen ist.
Auch im laufenden Betrieb passiert das: chrony und ähnliche Dienste gleichen kleine Abweichungen normalerweise sanft aus, sogenanntes Slewing, bei dem die Uhr über einen längeren Zeitraum leicht schneller oder langsamer läuft, statt abrupt zu springen. Größere Abweichungen, etwa nach einer längeren Netzwerktrennung oder einem manuellen date -s-Befehl durch einen Administrator, werden dagegen oft als abrupter Sprung angewendet, sogenanntes Stepping. Genau dieser Sprung ist die Ursache, weshalb Wall Clock-basierte Zeitmessung in Produktionssystemen als fehleranfällig gilt.
3. Monotonic Clock in der Praxis: uptime und clock_gettime
Unter Linux lässt sich der monotone Zähler auf mehreren Ebenen beobachten. Die Datei /proc/uptime zeigt in der ersten Spalte die Sekunden seit dem Systemstart, direkt abgeleitet aus der internen Monotonic Clock des Kernels. Für präzisere Messungen in eigenen Programmen steht die POSIX-Funktion clock_gettime(CLOCK_MONOTONIC, &ts) zur Verfügung, die eine Struktur mit Sekunden und Nanosekunden seit dem nicht spezifizierten Referenzpunkt zurückgibt. Der Nanosekunden-Anteil macht diese Funktion auch für hochauflösende Performance-Messungen innerhalb einzelner Funktionsaufrufe geeignet.
Auf Kommandozeilenebene liefert date +%s.%N zwar Nanosekunden-Präzision, basiert aber weiterhin auf der Wall Clock und ist damit für reine Laufzeitmessungen ungeeignet. Für Shell-Skripte, die Laufzeiten korrekt messen wollen, bietet sich stattdessen ein direkter Blick in /proc/uptime an, kombiniert mit einer einfachen Differenzberechnung zwischen Start- und Endwert.
#!/usr/bin/env bash
# Comparing wall clock vs monotonic time for duration measurement
set -euo pipefail
# WRONG for duration measurement: wall clock can jump backward
start_wall="$(date +%s.%N)"
sleep 2
end_wall="$(date +%s.%N)"
echo "Wall clock delta: $(echo "$end_wall - $start_wall" | bc) seconds"
# RIGHT: monotonic counter from /proc/uptime, immune to NTP jumps
start_mono="$(awk '{print $1}' /proc/uptime)"
sleep 2
end_mono="$(awk '{print $1}' /proc/uptime)"
echo "Monotonic delta: $(echo "$end_mono - $start_mono" | bc) seconds"
# A small C program calling clock_gettime(CLOCK_MONOTONIC, ...) directly
cat > /tmp/mono_demo.c << 'EOF'
#include <stdio.h>
#include <time.h>
int main(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("monotonic: %lld.%09ld\n", (long long)ts.tv_sec, ts.tv_nsec);
return 0;
}
EOF
gcc -o /tmp/mono_demo /tmp/mono_demo.c && /tmp/mono_demo
4. Auswirkungen auf Timeouts und Retry-Logik
Ein häufiges Bug-Muster in selbst geschriebenen Retry-Mechanismen: Ein Skript merkt sich den Startzeitpunkt mit date +%s, berechnet in einer Schleife wiederholt die verstrichene Zeit gegen einen Timeout-Wert und bricht ab, sobald dieser überschritten ist. Läuft während dieser Schleife eine NTP-Korrektur, die die Systemzeit um mehrere Minuten zurücksetzt, kann die berechnete Differenz plötzlich negativ werden oder unerwartet klein bleiben, wodurch der Timeout niemals auslöst und die Schleife weit über die eigentlich gewünschte Zeit hinaus läuft, im schlimmsten Fall unbegrenzt.
Springt die Wall Clock dagegen nach vorne, etwa durch eine grobe Fehlkorrektur, kann ein Timeout fälschlich sofort auslösen, obwohl in Wirklichkeit kaum Zeit vergangen ist. Beide Szenarien sind in der Praxis selten, aber genau deshalb schwer zu reproduzieren und zu debuggen, wenn sie einmal auftreten. Wer Timeouts, Retry-Intervalle oder Ratenbegrenzungen implementiert, sollte deshalb grundsätzlich die Monotonic Clock verwenden, niemals die Wall Clock, denn nur die Monotonic Clock garantiert, dass eine gemessene Differenz stets der tatsächlich vergangenen Zeit entspricht.
#!/usr/bin/env bash
# Timeout loop that survives an NTP time jump during execution
set -euo pipefail
readonly TIMEOUT_SECONDS=30
start_mono="$(awk '{print $1}' /proc/uptime)"
while true; do
now_mono="$(awk '{print $1}' /proc/uptime)"
elapsed="$(echo "$now_mono - $start_mono" | bc)"
if (( $(echo "$elapsed >= $TIMEOUT_SECONDS" | bc -l) )); then
echo "[ERROR] Timeout after ${elapsed}s, giving up" >&2
exit 1
fi
if curl -fsS -m 2 http://backend.local/health > /dev/null 2>&1; then
echo "[OK] Backend is reachable after ${elapsed}s"
break
fi
sleep 1
done
5. CLOCK_MONOTONIC_RAW und CLOCK_BOOTTIME: die Feinheiten
Neben der Standard-Monotonic-Clock kennt Linux zwei verwandte, aber unterschiedliche Varianten. CLOCK_MONOTONIC_RAW liefert die reine Hardware-Zählerzeit ohne jede NTP-Anpassung, auch nicht das sanfte Slewing, das CLOCK_MONOTONIC normalerweise erfährt. Diese Variante eignet sich für sehr präzise, kurzzeitige Messungen, bei denen selbst minimale Anpassungen durch den NTP-Dämon unerwünscht sind, etwa bei Benchmark-Messungen im Mikrosekundenbereich.
CLOCK_BOOTTIME unterscheidet sich in einem weiteren, oft übersehenen Detail: Während CLOCK_MONOTONIC während der Suspend-Phase eines Geräts, etwa eines Laptops im Standby, stehen bleibt, zählt CLOCK_BOOTTIME auch die Suspend-Zeit mit. Für Server ist dieser Unterschied meist irrelevant, da produktive Server üblicherweise nicht in den Suspend-Modus wechseln, für Laptops und mobile Geräte mit systemd-Timern kann er jedoch entscheidend sein, etwa wenn ein Timer nach dem Aufwachen aus dem Standby sofort nachgeholt werden soll.
6. Bash-Skripte robust gegen Zeitsprünge machen
Für die überwiegende Mehrheit administrativer Shell-Skripte reicht der bereits gezeigte Ansatz über /proc/uptime vollkommen aus, um Laufzeiten robust gegen Zeitsprünge zu messen. Ein zusätzliches, defensives Muster: Bei kritischen Skripten, deren Laufzeit überwacht werden soll, lohnt sich ein Vergleich zwischen der über die Monotonic Clock gemessenen Laufzeit und der über die Wall Clock gemessenen Laufzeit am Ende des Skripts. Eine große Diskrepanz zwischen beiden Werten deutet auf einen Zeitsprung während der Ausführung hin und kann als eigenständige Diagnoseinformation ins Log geschrieben werden.
Für Skripte, die Warteintervalle mit sleep umsetzen, ist die Monotonic Clock ohnehin implizit im Spiel: sleep selbst basiert intern auf einem monotonen Timer des Kernels und ist damit bereits robust gegen Wall-Clock-Sprünge, im Gegensatz zu selbst geschriebenen Warteschleifen, die die verstrichene Zeit manuell mit date berechnen, statt einfach sleep direkt zu nutzen.
7. Log-Zeitstempel: Wall Clock nötig, aber Vorsicht bei Deltas
Für Logdateien und Datenbank-Zeitstempel ist die Wall Clock unverzichtbar, denn ein Log-Eintrag muss zeigen, wann ein Ereignis im Kalendersinn tatsächlich stattgefunden hat, nicht wie viele Sekunden seit dem letzten Systemstart vergangen sind. Ein monotoner Zähler wäre für diesen Zweck bedeutungslos, da sein Referenzpunkt bei jedem Neustart auf null zurückspringt und keinerlei Bezug zu einem realen Kalenderdatum hat.
Vorsicht ist jedoch geboten, sobald aus zwei Wall-Clock-Zeitstempeln eine Zeitdifferenz berechnet werden soll, etwa um die Dauer zwischen zwei Log-Ereignissen zu bestimmen. Liegt zwischen beiden Zeitstempeln eine NTP-Korrektur, ist die berechnete Differenz nicht mehr die tatsächlich vergangene Zeit. Für reine Anzeige- und Aufbewahrungszwecke bleibt die Wall Clock also richtig, für die Berechnung von Zeitdifferenzen innerhalb eines laufenden Prozesses bleibt die Monotonic Clock die einzig korrekte Wahl.
8. systemd-Timer und die interne Monotonic-Logik
systemd-Timer-Units unterscheiden intern zwischen kalenderbasierten Ausdrücken wie OnCalendar, die auf der Wall Clock basieren, und monotonen Ausdrücken wie OnBootSec oder OnUnitActiveSec, die auf der Monotonic Clock basieren. Ein Timer mit OnUnitActiveSec=1h löst exakt eine Stunde nach dem letzten Start der zugehörigen Service-Unit aus, unabhängig davon, ob zwischenzeitlich die Wall Clock durch eine NTP-Korrektur verändert wurde, denn die interne Berechnung nutzt konsequent den monotonen Zähler des Kernels.
Dieser Unterschied erklärt, warum ein OnCalendar-Timer bei einer größeren manuellen Zeitkorrektur unter Umständen sofort oder mehrfach auslösen kann, während ein rein monotoner Timer von einer solchen Korrektur komplett unberührt bleibt. Für zeitkritische, wiederkehrende Wartungsintervalle, bei denen die exakte Wall-Clock-Uhrzeit irrelevant ist, aber ein zuverlässiger fester Abstand zwischen zwei Ausführungen zählt, ist ein monotoner Timer-Ausdruck deshalb oft die robustere Wahl gegenüber einem kalenderbasierten Ausdruck.
9. Clock-Typen im Überblick
Die folgende Tabelle fasst die wichtigsten Linux-Uhren und ihre jeweiligen Garantien zusammen, um die richtige Wahl je nach Anwendungsfall zu erleichtern.
| Clock-Typ | Kann springen? | Läuft während Suspend? | Typischer Einsatz |
|---|---|---|---|
| CLOCK_REALTIME | Ja, vor und zurück | Ja | Kalenderzeit, Log-Zeitstempel, Datenbanken |
| CLOCK_MONOTONIC | Nein, nur sanftes Slewing | Nein, stoppt bei Suspend | Timeouts, Laufzeitmessung, Retry-Logik |
| CLOCK_MONOTONIC_RAW | Nein, auch kein Slewing | Nein | Hochpräzise Benchmarks im Mikrosekundenbereich |
| CLOCK_BOOTTIME | Nein, nur sanftes Slewing | Ja, zählt Suspend-Zeit mit | Laptops, mobile Geräte, Timer nach Standby |
Als Faustregel gilt: Für alles, was angezeigt, gespeichert oder mit einem Kalenderdatum verglichen werden soll, ist CLOCK_REALTIME richtig. Für alles, was eine Dauer oder ein Zeitintervall misst, ist CLOCK_MONOTONIC oder eine ihrer Varianten die korrekte und robuste Wahl, unabhängig davon, wie oft die Systemzeit im Hintergrund korrigiert wird.
Mironsoft
Systemprogrammierung, Backend-Robustheit und Linux-Infrastruktur
Timeouts und Retry-Logik, die Zeitsprünge überstehen?
Wir prüfen bestehende Skripte und Anwendungen auf Wall-Clock-basierte Zeitmessung und ersetzen kritische Stellen durch robuste, monotone Zeitberechnung, die auch bei NTP-Korrekturen zuverlässig bleibt.
Code-Audit
Analyse auf fehleranfällige Wall-Clock-Nutzung in Timeouts und Retries
Refactoring
Umstellung kritischer Zeitmessungen auf CLOCK_MONOTONIC-basierte Logik
systemd-Timer-Design
Passende Wahl zwischen OnCalendar und monotonen Timer-Ausdrücken
10. Zusammenfassung
Monotonic Clock vs. Wall Clock ist der zentrale Unterschied zwischen zwei Uhren, die Linux gleichzeitig führt: CLOCK_REALTIME liefert die tatsächliche Kalenderzeit, kann sich aber durch NTP-Korrekturen oder manuelle Eingriffe jederzeit ändern, sogar rückwärts. CLOCK_MONOTONIC garantiert dagegen einen niemals rückläufigen Zählerwert und ist damit das einzig korrekte Werkzeug für Timeouts, Retry-Intervalle und Laufzeitmessungen.
Varianten wie CLOCK_MONOTONIC_RAW und CLOCK_BOOTTIME decken Spezialfälle wie Hochpräzisions-Benchmarks oder Suspend-bewusste Timer ab. Für Log-Zeitstempel und Datenbanken bleibt die Wall Clock unverzichtbar, für die Berechnung von Zeitdifferenzen innerhalb eines laufenden Prozesses gehört ausschließlich die Monotonic Clock zum Einsatz. Wer diese Unterscheidung konsequent einhält, vermeidet Bugs, die erst bei der nächsten größeren NTP-Korrektur sichtbar werden.
Monotonic Clock vs. Wall Clock, das Wichtigste auf einen Blick
Wall Clock kann springen
CLOCK_REALTIME kann durch NTP oder manuelle Eingriffe vor und zurück springen.
Monotonic Clock läuft immer vorwärts
CLOCK_MONOTONIC garantiert per Definition, niemals rückwärts zu laufen.
Timeouts brauchen Monotonic
Laufzeitmessung, Retry-Logik und Timeouts sollten ausschließlich Monotonic Clock nutzen.
Logs brauchen Wall Clock
Für Kalenderzeitstempel bleibt CLOCK_REALTIME nötig, für Deltas ist sie ungeeignet.