Hallo,
die Funktionen der C Standardbibliothek sind unter Linux nicht
echtzeitfähig (verwendet FUTEX), ich vermute mal, weil Sie Informationen
wie die Zeitzone vom OS benötigen.
Ich bin auf der Suche nach einer Implementierung die ich in einer RT
Umgebung verwenden kann. Kennt jemand eine?
Gruß
Philip
Philip schrieb:> die Zeitzone vom OS
Das OS hat keine Zeitzone. Die gibt's nur auf Anwendungsebene. Die
Systemzeit tickt in UTC.
Zum Auslesen der Uhr (Auflösung jenseits der normalen Taktrate) muss man
sicherstellen, dass nur ein Prozess aktiv ist.
Reentrante Versionen von localtime/gmtime gibt es als localtime_r /
gmtime_r. Die beiden müssen ja nichts im OS selbst machen, die laufen
nur in der Bibliothek.
Welche Funktionen brauchst Du genau? Was willst Du machen? Willst Du
wirklich eine Ortszeit haben oder reicht Dir UTC?
Wenn Dir UTC ausreicht, kannst Du das in einer halben Stunde selber
schreiben. Der einfachste Algorithmus trennt Datum von Uhrzeit
(Division) und zählt dann das Datum jahrweise, dann monatsweise hoch.
Grob formuliert ist das ungefähr wie folgt:
int schaltjahr(int jahr)
{
if(jahr%4!=0) return 0;
return 1; //ggf weitere Regeln beachten
}
int tageprojahr(int jahr)
{
if (schaltjahr(jahr)==1) return 366;
return 365;
}
int tagepromonat(int jahr, int monat)
{...
}
int x=Unix-Epoche eingangsvariable
int jahr=1970; //Ausgabevariable
int monat=0; //Ausgabevariable 0=Januar
int tag=0; //0=1.
int datum=x/(24*60*60);
int zeit=x%(24*60*60);
int stunde=zeit/3600;
int minute=(zeit/60)%60;
int sekunde=zeit%60;
int y=0; //Zählvariable
while ((y+tageprojahr(jahr))<x) {
jahr=jahr+1;
y=y+tageprojahr(jahr);
}
while ((y+tagepromonat(jahr,monat))<x) {
monat=monat+1;
y=y+tagepromonat(jahr, monat);
}
tag=x-y;
Willst Du Ortszeit, so wird das ein sehr viel größeres Projekt.
Allerdings kannst Du natürlich auch murksen und nur die Sommer- und
Winterzeitumstellung für eine Region und jetzt beachten, dann wird das
relativ simpel.
Jörg W. schrieb:> Das OS hat keine Zeitzone. Die gibt's nur auf Anwendungsebene.
Aber irgendwo muss die Zeitzone doch zentral gespeichert sein.
Auch für die reentrant Varianten habe ich vom OS-Hersteller (ist eine
firmeninterne RT-Variante von Linux) die Info bekommen, dass sie FUTEXe
verwenden.
Jörg W. schrieb:> Zum Auslesen der Uhr (Auflösung jenseits der normalen Taktrate) muss man> sicherstellen, dass nur ein Prozess aktiv ist.
Auf die laufenden Prozesse habe ich keinen Einfluss. Es geht aber auch
nicht um das Auslesen der Uhr. Ich habe einen Zeitstempel im EpochTime
Format, den ich in eine lesbare Zeit umwandeln muss.
Christian B. schrieb:> Wenn Dir UTC ausreicht, kannst Du das in einer halben Stunde selber> schreiben.
UTC würde mir reichen. Danke für die Anregung. Ich hatte die eigene
Implementierung als letzte Option natürlich auf dem Schirm. Aber
Zeitberechnungen haben eben so ihre Fallstricke, schon deshalb suche ich
erstmal nach einem erprobten Algorithmus.
Philip schrieb:> Jörg W. schrieb:>> Das OS hat keine Zeitzone. Die gibt's nur auf Anwendungsebene.> Aber irgendwo muss die Zeitzone doch zentral gespeichert sein.
Ja, in /etc/localtime.
> Auf die laufenden Prozesse habe ich keinen Einfluss. Es geht aber auch> nicht um das Auslesen der Uhr.
Ok, das habe ich dann auch falsch verstanden.
Philip schrieb:> Jörg W. schrieb:>> Das OS hat keine Zeitzone. Die gibt's nur auf Anwendungsebene.> Aber irgendwo muss die Zeitzone doch zentral gespeichert sein.
Es gibt eine Default-Zeitzone, deren Speicherort hat dir Rolf schon
geschrieben. Die lässt sich aber durch eine Environmentvariable
überschreiben.
> Auch für die reentrant Varianten habe ich vom OS-Hersteller (ist eine> firmeninterne RT-Variante von Linux) die Info bekommen, dass sie FUTEXe> verwenden.
Hast du denn keinen Sourcecode? Linux sollte doch Opensource sein.
Habe bei FreeBSD mal nachgeschaut, die benutzen die übliche
"Olson"-Bibliothek dafür. Dort gibt es in der Tat einen Lock, aber das
ist lediglich ein read lock, der nur dann blockiert, wenn jemand zur
gleichen Zeit irgendwas mit tzset() anstellt. Sofern du also garantieren
kannst, dass niemand tzset() oder tzsetwall() jemals aufruft, sollte
dich dieser Lock nicht stören.
UTSL :-)
Philip schrieb:> Aber> Zeitberechnungen haben eben so ihre Fallstricke, schon deshalb suche ich> erstmal nach einem erprobten Algorithmus.
Wenn Du so was wie die "UNIX-Epoche" verwendest rechnest Du einfach mit
Integern. Somit ist das relativ einfach.
Um die Zeit selbst zu zählen brauchst Du halt eine ISR. In dieser machst
Du im Prinzip so was hier:
static int zeit=0;
static int usec=0;
static int intervall=geschätzter ISR Intervall in µsec
void isr()
{
usec=usec+intervall;
if (usec>1000000) {
zeit=zeit+1;
usec=usec-1000000;
}
}
Wenn Du die Zeit abgleichst, kannst Du einen Teil des Zeitfehlers auf
zeit und usec anpassen, und einen anderen Teil auf intervall, so dass Du
den Gangfehler Deines Quarzes ausgleichen kannst. Besonders bei kleinen
Fehlern bietet es sich an nur intervall anzupassen.
Ich weiß nicht welchen Standard dein Compiler kann, aber vielleicht
findest du hier was.
Das baut auf std::chrono auf und benötigt C++11.
https://howardhinnant.github.io/date/date.html
Das ist nicht so ganz einfach. Die richige Funktion für dich ist
vermutlich clock_gettime(). Das ganze drumherum ist in der glibc, also
userspace. Auch die ganzen C++ std::chrono Klassen sind mit
clock_gettime implementiert (einfach mal in die sourcen schauen...).
clock_gettime() und einige andere dergleichen Funkionen sehen vielleicht
wie syscalls aus, machen auf einem normalen System aber überhaupt keine.
Syscalls (bzw. die userspace/kernelspace switche) brauchen lange, was
schlecht ist. Der Kernel stellt jedem Prozess daher die "vdso" zur
Verfügung. Das ist eine normale shared library, die nicht physisch auf
Platte liegt, sondern vom Kernel/Loader kommt. Diese implementiert die
clock_gettime() und andere. Dazu mappt der Kernel den "vvar" Bereich in
den Prozess. Da ist ein Kernel-struct drinnen, das die ganzen
Kompensationsparameter für die Uhrzeitberechnung enthält.
Siehe die letzten beiden Einträge:
vvar ist (read-only für deinen Prozess) Kernel-Speicher und der Kernel
aktualisiert regelmäßig die Parameter. Die clock_gettime()
Implementierung verwendet hauptsächlich die CPU counter (rdtsc). Die TSC
Geschwindigkeit kann sich aber ändern (driften), z.B. mit der
Temperatur, daher braucht man regelmäßig einen Abgleich des Taktes. Und
diese Parameter legt der Kernel in regelmäßigen Abständen in das struct.
Auf diese Art und Weise braucht clock_gettime() normalerweise (99.99%...
keine Ahnung viel viel genau, aber wirklich fast immer!) sehr wenig
Zeit, so ca. 28 ns. Das wäre mit syscalls nicht zu machen. AFAIK ist das
gleich bei normalen und -RT Linux Kernels (kann das sicher aber nur für
linux-rt und Server CPUs sagen).
Jetzt der schwierigere Teil:
clock_gettime() ist nur meistens so schnell. In seltenen Fällen braucht
ein einzelner Aufruf auch mal ~10 us (drei Größenordnungen!). Das
passiert wenn der Kernel das struct im vvar aktualisiert und es dazu
locken muss (zu viele Daten für atomaren Zugriff). Dein userspace
clock_gettime() wird also so lange warten, bis der Kernel das struct
wieder freigegeben hat.
Siehe der code im do{}while() hier:
https://elixir.bootlin.com/linux/latest/source/arch/x86/entry/vdso/vclock_gettime.c#L159
Weiter unten in der selben Datei findet man auch die anderen Aufrufe,
die im vdso sind, z.B. time, clock_gettime, gettimeofday...
Und du kannst auch sehen, dass der Kernel auf den traditionellen syscall
urückfällt, wenn du den vdso-Support nicht hinein kompiliert hast. Zudem
funktioniert das vdso auch nur bei manchen Uhrtypen, z.B.
CLOCK_MONOTONIC und CLOCK_REALTIME. Beispielsweise wird bei CLOCK_TAI
aber ein syscall gemacht.
Wenn du gelegentlich mal clock_gettime() aufrufst, dann kann dir der
Ausreißer in der Ausführungsdauer vermutlich egal sein. Der Rest deines
Systems wird viel mehr Jittern und die Uhr driften. Wenn du ein mit PTP
auf <1 us genau synchronisiertes Regelungssystem hast und auf wenige us
genau Dinge machen möchtest, dann kann das schon mal blöd sein (oder man
muss damit leben).
Philip schrieb:> Christian B. schrieb:>> Wenn Dir UTC ausreicht, kannst Du das in einer halben Stunde selber>> schreiben.> UTC würde mir reichen. Danke für die Anregung. Ich hatte die eigene> Implementierung als letzte Option natürlich auf dem Schirm. Aber> Zeitberechnungen haben eben so ihre Fallstricke, schon deshalb suche ich> erstmal nach einem erprobten Algorithmus.
Würde ich niemals selbst implementieren. Die ganzen Sonderfälle
(Schaltjahre, Schaltsekunden) wird man selber nicht richtig hinbekommen.
Bei UTC: sei dir im klaren, das die UTC Schaltsekunden besitzt. Die
POSIX clock wird in dem Fall zwar nicht eine Sekunde zurückspringen,
aber sie wird den Tick während der Schaltsekunde halb so schnell machen.
Also zwei SI-Sekunden lang. Wenn du eine kontinuierliche und absolute
Zeitbasis brauchst, wirst du um TAI (Temps Atomique International,
International Atomic Time) nicht herum kommen.
Zuständig für Schaltsekunden ist übrigens der International Earth
Rotation and Reference Systems Service (IERS, https://www.iers.org/).
Die beobachten den Fehler der UTC zur UT1 (die ΔUT1 oder DUT1) und
entscheiden jedes habe Jahr, ob zum Ende des Halbjahres (Juni/Dezember)
eine weitere Schaltsekunde fällig wird. Die Entscheidungen werden im
Bulletin C bekannt gegeben.
https://www.iers.org/SharedDocs/News/EN/BulletinC.html
Und der aktuelle:
https://datacenter.iers.org/data/latestVersion/16_BULLETIN_C16.txt
1
NO leap second will be introduced at the end of December 2019.
2
The difference between Coordinated Universal Time UTC and the
3
International Atomic Time TAI is :
4
5
from 2017 January 1, 0h UTC, until further notice : UTC-TAI = -37 s
Jiri D. schrieb:> Die richige Funktion für dich ist vermutlich clock_gettime().
Nein: er will gar keine Uhr auslesen, sondern nur einen Timestamp in
lesbare Form bringen.
Siehe oben, nach Review des Olson-Sourcecodes bin ich der Meinung, dass
er das machen kann, da es zwar einen read lock gibt, aber die Bedingung,
unter welcher dieser blockieren könnte, völlig klar und überschaubar
ist.
Vielen Dank für die rege Beteiligung.
Um es noch mal deutlich zu machen - ich habe einen Zeitstempel, der
bereits im UTC Format vorliegt. Damit sollte eine einfache Umrechnung
unter Berücksichtigung der Schaltjahre ja eigentlich kein Problem sein.
Warum die Verwendung von gmtime_r() Probleme macht kann ich nicht sagen.
Aber ich kann sehen, dass andere Prozesse/Threads ein nicht akzeptables
Zeitverhalten haben, wenn ich sie verwende. Ich ich denke werde jetzt
also die Umrechnung auf Basis des Vorschlags von Christian B. selbst
implementieren.
@Jörg W.
möglicherweise wird in unserem Fall ja nicht die Olson-Bibliothek
verwendet. Ich versuche mal das bei den OS-Entwicklern in Erfahrung zu
bringen.
Philip schrieb:> Warum die Verwendung von gmtime_r() Probleme macht kann ich nicht sagen.
Einfach mal in die Quelle schauen. GLibC ist Open Source.
Dort findet man relativ bald die Ursache im Time Zone Handling:
1
/* Return the `struct tm' representation of *TIMER in the local timezone.
2
Use local time if USE_LOCALTIME is nonzero, UTC otherwise. */
Also kein Wunder dass das im Real Time Pfad knallt. Die Umrechnung von
Zeitzonen ist überraschend aufwändig.
Daher auch die Verwunderung hier dass Du das ausgerechnet in Real Time
machen willst. Dort sollte man sowas vermeiden wenn irgendwie möglich.
Die Umrechnung sollte erst nahe am oder im UI erfolgen, das dann nicht
mehr Real Time sein muss.
Jim M. schrieb:> Also kein Wunder dass das im Real Time Pfad knallt.
Was mich wundert: __libc_lock_lock() scheint nicht zwischen read lock
und write lock zu unterscheiden, oder täusche ich mich da?
Wenn dem so ist, dann ist die glibc hier drastisch schlechter als die
Olson-Bibliothek. Diese hat, wie beschrieben, an dieser Stelle nur einen
read lock, der nur dann ausbremsen würde, wenn zur gleichen Zeit jemand
in einem anderen Thread versucht, die Zeitzone neu einzustellen. Lesend
kann man die einmal vorgenommene Einstellung beliebig oft parallel
zugreifen.
Vielleicht ja ansonsten einfach auf die Olson-Bibliothek dafür
ausweichen, die ist schließlich ebenso Opensource (wird im FreeBSD nur
als 3rd-party-Code hinzugezogen).
Ich bin jetzt nicht sicher, ob genau das mit der "Olson Bibliothek"
gemeint ist, aber die IANA bietet selber auch etwas Source-Code für ihre
Timezone-Datenbanken an:
https://www.iana.org/time-zones
Der Coding-Style ist arg gewöhnungsbedürftig, aber damit kann man sich
etwas bauen, was erstmal unabhängig von den C-Bibliotheken ist, und
zumindest unter Berücksichtgung der Fallstricke richtig rechnet
(netterweise auch, wenn es doch mal was anderes als UTC sein soll, was
es ja fast automatisch wird, wenn irgend ein Altags-Mensch das ganze mal
zu Gesicht bekommen soll.)
Falls die zu benutzende Zeitzone von vornherein feststeht, könnte man
sich natürlich eine Variante bauen, in der genau diese statisch
eincompiliert wird. Dann braucht man keine Locks mehr.